Files
api/web/store/errors.ts
Michael Datelle d74d9f1246 test: create tests for stores batch 2 (#1351)
This is batch 2 of the web store tests. I started implementing the
recommended approach in the Pinia docs for the store tests and was able
to eliminate most of the mocking files. The server.test.ts file still
uses `pinia/testing` for now since I was having trouble with some of the
dependencies in the store due to it's complexity.

I also updated the `web-testing-rules`for Cursor in an effort to
streamline the AI's approach when helping with tests. There's some
things it still struggles with and seems like it doesn't always take the
rules into consideration until after it hits a snag. It likes to try and
create a mock for the store it's actually testing even though there's a
rule in place and has to be reminded.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **Tests**
- Added comprehensive test suites for account management and activation
flows to ensure smoother user interactions.
- Introduced new test coverage for callback actions, validating state
management and action handling.
- Added a new test file for the account store, covering various actions
and their interactions.
- Introduced a new test file for the activation code store, verifying
state management and modal visibility.
- Established a new test file for dropdown functionality, ensuring
accurate state management and action methods.
- Added a new test suite for the Errors store, covering error handling
and modal interactions.
- Enhanced test guidelines for Vue components and Pinia stores,
providing clearer documentation and best practices.
- Introduced a new test file for the InstallKey store, validating key
installation processes and error handling.
- Added a new test file for the dropdown store, ensuring accurate
visibility state management and action methods.

- **Refactor**
- Streamlined the structure of account action payloads for consistent
behavior.
- Improved code formatting and reactivity setups to enhance overall
operational stability.
- Enhanced reactivity capabilities in various stores by introducing new
Vue composition API functions.
- Improved code readability and organization in the installKey store and
other related files.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: mdatelle <mike@datelle.net>
2025-04-14 12:30:27 -04:00

149 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ref } from 'vue';
import { createPinia, defineStore, setActivePinia } from 'pinia';
import { OBJ_TO_STR } from '~/helpers/functions';
import type { BrandButtonProps } from '@unraid/ui';
import type { Server } from '~/types/server';
/**
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
* @see https://github.com/vuejs/pinia/discussions/1085
*/
setActivePinia(createPinia());
export type ErrorType =
| 'account'
| 'callback'
| 'installKey'
| 'request'
| 'server'
| 'serverState'
| 'unraidApiGQL'
| 'unraidApiState';
export interface Error {
actions?: BrandButtonProps[];
debugServer?: Server;
forumLink?: boolean;
heading: string; // if adding new errors be sure to add translations key value pairs
level: 'error' | 'info' | 'warning';
message: string;
ref?: string;
supportLink?: boolean;
type: ErrorType;
}
export const useErrorsStore = defineStore('errors', () => {
const errors = ref<Error[]>([]);
// const displayedErrors = computed(() => errors.value.filter(error => error.type === 'server' || error.type === 'serverState'));
const removeErrorByIndex = (index: number) => {
errors.value = errors.value.filter((_error, i) => i !== index);
};
const removeErrorByRef = (ref: string) => {
errors.value = errors.value.filter((error) => error?.ref !== ref);
};
const resetErrors = () => {
errors.value = [];
};
const setError = (error: Error) => {
console.error('[setError]', error);
errors.value.push(error);
};
interface TroubleshootPayload {
email: string;
includeUnraidApiLogs: boolean;
}
const openTroubleshoot = async (payload: TroubleshootPayload) => {
try {
// @ts-expect-error `FeedbackButton` will be included in 6.10.4+ DefaultPageLayout
await FeedbackButton();
// once the modal is visible we need to select the radio to correctly show the bug report form
let $modal = document.querySelector('.sweet-alert.visible');
while (!$modal) {
await new Promise((resolve) => setTimeout(resolve, 100));
$modal = document.querySelector('.sweet-alert.visible');
}
// autofill errors into the bug report form
if (errors.value.length) {
let $textarea: HTMLInputElement | null = $modal.querySelector('#troubleshootDetails');
while (!$textarea) {
await new Promise((resolve) => setTimeout(resolve, 100));
$textarea = $modal.querySelector('#troubleshootDetails');
}
const errorMessages = errors.value
.map((error, index) => {
const index1 = index + 1;
let message = `• Error ${index1}: ${error.heading}\n`;
message += `• Error ${index1} Message: ${error.message}\n`;
message += `• Error ${index1} Level: ${error.level}\n`;
message += `• Error ${index1} Type: ${error.type}\n`;
if (error.ref) {
message += `• Error ${index1} Ref: ${error.ref}\n`;
}
if (error.debugServer) {
message += `• Error ${index1} Debug Server:\n${OBJ_TO_STR(error.debugServer)}\n`;
}
return message;
})
.join('\n***************\n');
$textarea.value += '\n##########################\n';
$textarea.value += `# Debug Details Component Errors ${errors.value.length} #\n`;
$textarea.value += '##########################\n';
$textarea.value += errorMessages;
}
// autofill emails
let $emailInput: HTMLInputElement | null = $modal.querySelector('#troubleshootEmail');
while (!$emailInput) {
await new Promise((resolve) => setTimeout(resolve, 100));
$emailInput = $modal.querySelector('#troubleshootEmail');
}
if (payload.email) {
$emailInput.value = payload.email;
} else {
$emailInput.focus();
}
// select the radio to correctly show the bug report form
let $myRadio: HTMLInputElement | null = $modal.querySelector('#optTroubleshoot');
while (!$myRadio) {
await new Promise((resolve) => setTimeout(resolve, 100));
$myRadio = $modal.querySelector('#optTroubleshoot');
}
$myRadio.checked = true;
// show the correct form in the modal
let $panels = $modal.querySelectorAll('.allpanels');
while (!$panels) {
await new Promise((resolve) => setTimeout(resolve, 100));
$panels = $modal.querySelectorAll('.allpanels');
}
$panels.forEach(($panel: Element) => {
if ($panel.id === 'troubleshoot_panel') {
// @ts-expect-error - the webgui feedback modal has different "panel" elements, so we'll automatically select the troubleshooting option for the user
$panel.style.display = 'block';
} else {
// @ts-expect-error - the webgui feedback modal has different "panel" elements, so we'll automatically select the troubleshooting option for the user
$panel.style.display = 'none';
}
});
} catch (error) {
console.error('[openTroubleshoot]', error);
}
};
return {
errors,
removeErrorByIndex,
removeErrorByRef,
resetErrors,
setError,
openTroubleshoot,
};
});