CI/CD / preconditions (pull_request) Successful in 1s
Native JavaScript checks / Native launcher and release tests (pull_request) Successful in 1m2s
macOS checks / Native Rust tests and unsigned build (pull_request) Successful in 1m38s
CI/CD / test_and_check_app (pull_request) Successful in 6m47s
CI/CD / Publish immutable image (pull_request) Skipped
249 lines
8.0 KiB
JavaScript
249 lines
8.0 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';
|
|
import { createInertiaApp } from '@inertiajs/vue3';
|
|
|
|
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 { createLocalization } = await server.ssrLoadModule(
|
|
'/resources/js/lib/localization.ts',
|
|
);
|
|
const { createAppearance } = await server.ssrLoadModule(
|
|
'/resources/js/composables/useAppearance.ts',
|
|
);
|
|
const { initializeNativeApp, useNativeApp, registerNativeRecovery } =
|
|
await server.ssrLoadModule('/resources/js/composables/useNativeApp.ts');
|
|
const { isNativeAppState } = await server.ssrLoadModule(
|
|
'/resources/js/lib/native-app.ts',
|
|
);
|
|
const App = (
|
|
await server.ssrLoadModule('/resources/js/pages/settings/App.vue')
|
|
).default;
|
|
const Appearance = (
|
|
await server.ssrLoadModule(
|
|
'/resources/js/pages/settings/Appearance.vue',
|
|
)
|
|
).default;
|
|
const render = async (signedIn = false, component = App) => {
|
|
const result = await createInertiaApp({
|
|
page: {
|
|
component: 'settings/App',
|
|
url: '/settings/app',
|
|
version: null,
|
|
clearHistory: false,
|
|
encryptHistory: false,
|
|
props: {
|
|
name: 'KanbanGit',
|
|
errors: {},
|
|
sidebarOpen: true,
|
|
appearance: 'system',
|
|
auth: {
|
|
user: signedIn
|
|
? { name: 'Test', email: 'test@example.test' }
|
|
: null,
|
|
permissions: {
|
|
globalAdmin: false,
|
|
projectCreate: false,
|
|
canManageTeams: false,
|
|
},
|
|
},
|
|
navigation: { project: null },
|
|
},
|
|
},
|
|
resolve: () => component,
|
|
render: renderToString,
|
|
setup: ({ App, props, plugin }) =>
|
|
createSSRApp({ render: () => h(App, props) })
|
|
.use(plugin)
|
|
.use(createLocalization())
|
|
.use(createAppearance('system')),
|
|
});
|
|
return result.body;
|
|
};
|
|
await initializeNativeApp();
|
|
assert.match(await render(), /compatible KanbanGit app/);
|
|
assert.doesNotMatch(
|
|
await render(true, Appearance),
|
|
/href="\/settings\/app"/,
|
|
);
|
|
let calls = [];
|
|
let current = {
|
|
apiVersion: 1,
|
|
platform: 'ios',
|
|
version: '1.0.0',
|
|
workspace: 'https://example.test/app/',
|
|
capabilities: ['workspace'],
|
|
};
|
|
const bridge = {
|
|
version: 2,
|
|
request: async (action, payload) => {
|
|
calls.push([action, payload]);
|
|
return structuredClone(current);
|
|
},
|
|
};
|
|
globalThis.window = { kanbanNative: bridge };
|
|
await initializeNativeApp();
|
|
assert.equal(calls.length, 0);
|
|
bridge.version = 1;
|
|
current.apiVersion = 2;
|
|
await initializeNativeApp();
|
|
assert.equal(useNativeApp().state.value, null);
|
|
current.apiVersion = 1;
|
|
await initializeNativeApp();
|
|
assert.equal(calls.at(-1)[0], 'settingsReady');
|
|
delete globalThis.window;
|
|
let html = await render();
|
|
assert.match(html, /value="https:\/\/example.test\/app\/"/);
|
|
assert.doesNotMatch(
|
|
html,
|
|
/Check for updates now|Install update|native-update-interval/,
|
|
);
|
|
assert.match(await render(true, Appearance), /href="\/settings\/app"/);
|
|
current = {
|
|
...current,
|
|
platform: 'macos',
|
|
capabilities: ['workspace', 'updates'],
|
|
updates: {
|
|
phase: 'available',
|
|
currentVersion: '1.0.0',
|
|
build: '1',
|
|
version: '1.1.0',
|
|
notes: '<script>bad()</script>',
|
|
downloaded: 0,
|
|
total: null,
|
|
error: null,
|
|
preferences: { automatic: true, intervalHours: 6 },
|
|
},
|
|
};
|
|
globalThis.window = { kanbanNative: bridge };
|
|
await useNativeApp().poll();
|
|
delete globalThis.window;
|
|
html = await render(true);
|
|
assert.match(html, /Check for updates now/);
|
|
assert.match(html, /Install update/);
|
|
assert.match(html, /<script>bad/);
|
|
assert.doesNotMatch(html, /<script>bad/);
|
|
assert.match(html, /id="native-update-interval"[^>]*value="6"/);
|
|
for (const phase of [
|
|
'checking',
|
|
'downloading',
|
|
'verifying',
|
|
'installing',
|
|
'restarting',
|
|
'error',
|
|
'current',
|
|
]) {
|
|
current.updates.phase = phase;
|
|
current.updates.downloaded = 40;
|
|
current.updates.total = 100;
|
|
globalThis.window = { kanbanNative: bridge };
|
|
await useNativeApp().poll();
|
|
if (
|
|
[
|
|
'checking',
|
|
'downloading',
|
|
'verifying',
|
|
'installing',
|
|
'restarting',
|
|
].includes(phase)
|
|
) {
|
|
const before = calls.length;
|
|
assert.equal(await useNativeApp().act('checkForUpdates'), false);
|
|
assert.equal(calls.length, before);
|
|
}
|
|
delete globalThis.window;
|
|
html = await render();
|
|
if (phase === 'downloading') {
|
|
assert.match(html, /40%/);
|
|
}
|
|
}
|
|
for (const invalid of [
|
|
null,
|
|
{},
|
|
{ ...current, apiVersion: 2 },
|
|
{ ...current, platform: 'unsupported' },
|
|
{ ...current, updates: { ...current.updates, phase: 'madeUp' } },
|
|
]) {
|
|
assert.equal(isNativeAppState(invalid), false);
|
|
}
|
|
globalThis.window = { kanbanNative: bridge };
|
|
calls = [];
|
|
const listeners = {};
|
|
globalThis.window.addEventListener = (name, callback) => {
|
|
listeners[name] = callback;
|
|
};
|
|
globalThis.document = {
|
|
addEventListener: (name, callback) => {
|
|
listeners[name] = callback;
|
|
},
|
|
};
|
|
registerNativeRecovery();
|
|
for (const name of ['offline', 'inertia:exception', 'inertia:invalid']) {
|
|
listeners[name]({ detail: { response: { status: 503 } } });
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
assert.equal(calls.at(-1)[0], 'openSettings');
|
|
}
|
|
const count = calls.length;
|
|
listeners['inertia:invalid']({ detail: { response: { status: 200 } } });
|
|
assert.equal(calls.length, count);
|
|
delete globalThis.document;
|
|
calls = [];
|
|
await useNativeApp().act('setUpdatePreferences', {
|
|
automatic: false,
|
|
intervalHours: 24,
|
|
});
|
|
assert.deepEqual(calls[0], [
|
|
'setUpdatePreferences',
|
|
{ automatic: false, intervalHours: 24 },
|
|
]);
|
|
await useNativeApp().act('setWorkspace', { server: 'https://new.test/' });
|
|
assert.deepEqual(calls.at(-1), [
|
|
'setWorkspace',
|
|
{ server: 'https://new.test/' },
|
|
]);
|
|
let resolve;
|
|
bridge.request = () =>
|
|
new Promise((done) => {
|
|
resolve = done;
|
|
});
|
|
const first = useNativeApp().act('setWorkspace', {
|
|
server: 'https://new.test/',
|
|
});
|
|
assert.equal(
|
|
await useNativeApp().act('setWorkspace', {
|
|
server: 'https://duplicate.test/',
|
|
}),
|
|
false,
|
|
);
|
|
resolve(null);
|
|
await first;
|
|
bridge.request = async () => {
|
|
throw new Error('invalidServer');
|
|
};
|
|
assert.equal(
|
|
await useNativeApp().act('setWorkspace', {
|
|
server: 'http://invalid.test',
|
|
}),
|
|
false,
|
|
);
|
|
delete globalThis.window;
|
|
assert.match(await render(), /Enter a valid HTTPS/);
|
|
console.log('Native app settings checks passed.');
|
|
} finally {
|
|
delete globalThis.window;
|
|
await server.close();
|
|
}
|