172 lines
5.9 KiB
JavaScript
172 lines
5.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { fileURLToPath } from 'node:url';
|
|
import vue from '@vitejs/plugin-vue';
|
|
import { createServer } from 'vite';
|
|
import { createSSRApp, h } from 'vue';
|
|
import { renderToString } from 'vue/server-renderer';
|
|
|
|
const server = await createServer({
|
|
configFile: false,
|
|
plugins: [vue()],
|
|
resolve: {
|
|
alias: {
|
|
'@': fileURLToPath(new URL('../../resources/js', import.meta.url)),
|
|
},
|
|
},
|
|
server: { middlewareMode: true, hmr: false, watch: null },
|
|
appType: 'custom',
|
|
});
|
|
|
|
try {
|
|
const { isFormNetworkFailure, matchesFormRequest, formRequestHeader } =
|
|
await server.ssrLoadModule('/resources/js/lib/form-feedback.ts');
|
|
const config = { headers: { [formRequestHeader]: 'submission-a' } };
|
|
for (const code of ['ERR_NETWORK', 'ECONNABORTED', 'ETIMEDOUT']) {
|
|
assert.equal(
|
|
isFormNetworkFailure(
|
|
{ isAxiosError: true, code, config },
|
|
'submission-a',
|
|
),
|
|
true,
|
|
);
|
|
}
|
|
for (const error of [
|
|
null,
|
|
new Error('Render failed'),
|
|
{ code: 'ERR_NETWORK', config },
|
|
{ isAxiosError: true, code: 'ERR_CANCELED', config },
|
|
{
|
|
isAxiosError: true,
|
|
code: 'ERR_NETWORK',
|
|
config,
|
|
response: { status: 500 },
|
|
},
|
|
{ isAxiosError: true, code: 'ERR_NETWORK', config: {} },
|
|
]) {
|
|
assert.equal(isFormNetworkFailure(error, 'submission-a'), false);
|
|
}
|
|
assert.equal(
|
|
isFormNetworkFailure(
|
|
{ isAxiosError: true, code: 'ERR_NETWORK', config },
|
|
'submission-b',
|
|
),
|
|
false,
|
|
);
|
|
assert.equal(matchesFormRequest(config, null), false);
|
|
assert.equal(matchesFormRequest(config, 'submission-a'), true);
|
|
assert.equal(matchesFormRequest(undefined, 'submission-a'), false);
|
|
|
|
const { createLocalization } = await server.ssrLoadModule(
|
|
'/resources/js/lib/localization.ts',
|
|
);
|
|
const { en } = await server.ssrLoadModule('/resources/js/locales/en.ts');
|
|
const { default: FeedbackState } = await server.ssrLoadModule(
|
|
'/resources/js/components/FeedbackState.vue',
|
|
);
|
|
const { default: FormFeedback } = await server.ssrLoadModule(
|
|
'/resources/js/components/FormFeedback.vue',
|
|
);
|
|
const { default: InputError } = await server.ssrLoadModule(
|
|
'/resources/js/components/InputError.vue',
|
|
);
|
|
const { default: AuthFormFeedback } = await server.ssrLoadModule(
|
|
'/resources/js/components/AuthFormFeedback.vue',
|
|
);
|
|
const { default: PasswordInput } = await server.ssrLoadModule(
|
|
'/resources/js/components/PasswordInput.vue',
|
|
);
|
|
const localization = createLocalization(
|
|
{ locale: 'de' },
|
|
{
|
|
de: {
|
|
...en,
|
|
'common.retry': 'Erneut versuchen',
|
|
'feedback.empty': 'Noch keine Einträge',
|
|
'auth.feedback.expiredTitle': 'Sitzung abgelaufen',
|
|
'auth.password.show': 'Passwort anzeigen',
|
|
},
|
|
},
|
|
);
|
|
const render = (component, props) =>
|
|
renderToString(
|
|
createSSRApp({ render: () => h(component, props) }).use(
|
|
localization,
|
|
),
|
|
);
|
|
assert.match(
|
|
await render(FeedbackState, { state: 'empty' }),
|
|
/Noch keine Einträge/,
|
|
);
|
|
const loading = await render(FormFeedback, { state: 'loading' });
|
|
assert.match(loading, /role="status"/);
|
|
assert.match(loading, /aria-busy="true"/);
|
|
assert.match(loading, /motion-reduce:animate-none/);
|
|
assert.doesNotMatch(loading, /<button/);
|
|
const validation = await render(FormFeedback, { state: 'validation' });
|
|
assert.match(validation, /role="alert"/);
|
|
assert.match(validation, /Check your entries/);
|
|
assert.doesNotMatch(validation, /<button/);
|
|
for (const state of ['network', 'server', 'cancelled']) {
|
|
const html = await render(FormFeedback, { state });
|
|
assert.match(html, /Erneut versuchen/);
|
|
assert.match(html, /type="button"/);
|
|
assert.match(html, /role="alert"/);
|
|
}
|
|
assert.match(
|
|
await render(FormFeedback, { state: 'network' }),
|
|
/Connection interrupted/,
|
|
);
|
|
assert.match(
|
|
await render(FormFeedback, { state: 'server' }),
|
|
/unexpected response/,
|
|
);
|
|
assert.match(
|
|
await render(FormFeedback, { state: 'success' }),
|
|
/Changes saved/,
|
|
);
|
|
assert.doesNotMatch(await render(FormFeedback, { state: 'idle' }), /role=/);
|
|
const fieldError = await render(InputError, {
|
|
message: '<script>alert(1)</script>',
|
|
});
|
|
assert.match(fieldError, /role="alert"/);
|
|
assert.match(fieldError, /text-destructive/);
|
|
assert.doesNotMatch(fieldError, /<script>/);
|
|
const expired = await render(AuthFormFeedback, {
|
|
state: 'server',
|
|
responseStatus: 419,
|
|
});
|
|
assert.match(expired, /Sitzung abgelaufen/);
|
|
assert.match(expired, /href="\/login"/);
|
|
assert.doesNotMatch(expired, /Erneut versuchen/);
|
|
assert.match(
|
|
await render(AuthFormFeedback, {
|
|
state: 'server',
|
|
responseStatus: 429,
|
|
}),
|
|
/Too many attempts/,
|
|
);
|
|
assert.match(
|
|
await render(AuthFormFeedback, {
|
|
state: 'loading',
|
|
loadingTitle: 'Signing in',
|
|
}),
|
|
/Signing in/,
|
|
);
|
|
assert.match(
|
|
await render(AuthFormFeedback, { state: 'validation' }),
|
|
/Check your details/,
|
|
);
|
|
const password = await render(PasswordInput, {
|
|
disabled: true,
|
|
autocomplete: 'current-password',
|
|
});
|
|
assert.match(password, /Passwort anzeigen/);
|
|
assert.match(password, /autocomplete="current-password"/);
|
|
assert.match(password, /aria-pressed="false"/);
|
|
assert.doesNotMatch(password, /tabindex="-1"/);
|
|
assert.match(password, /<button[^>]*disabled/);
|
|
console.log('Form feedback behavior checks passed.');
|
|
} finally {
|
|
await server.close();
|
|
}
|