264 lines
9.9 KiB
JavaScript
264 lines
9.9 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import process from 'node:process';
|
|
|
|
const { chromium } = await import(
|
|
process.env.PLAYWRIGHT_MODULE ?? 'playwright'
|
|
);
|
|
const browser = await chromium.launch({ channel: 'chrome', headless: true });
|
|
const base = 'http://127.0.0.1:4187';
|
|
const context = await browser.newContext({
|
|
viewport: { width: 1440, height: 1000 },
|
|
});
|
|
const page = await context.newPage();
|
|
page.setDefaultTimeout(10000);
|
|
const errors = [];
|
|
page.on('pageerror', (error) => errors.push(error.message));
|
|
const mutations = [];
|
|
page.on('request', (request) => {
|
|
if (['POST', 'DELETE'].includes(request.method())) {
|
|
mutations.push(request);
|
|
}
|
|
});
|
|
const dialog = () => page.getByRole('dialog');
|
|
const button = (name) => page.getByRole('button', { name, exact: true });
|
|
const confirmReset = () =>
|
|
dialog().getByRole('button', { name: 'Reset to template', exact: true });
|
|
const result = (value) =>
|
|
context.addCookies([{ name: 'general_test_result', value, url: base }]);
|
|
const visit = async (query = '?reset') => {
|
|
await result('success');
|
|
await page.goto(base + '/projects/1/settings/general' + query);
|
|
await page.getByRole('heading', { name: 'General', exact: true }).waitFor();
|
|
};
|
|
const noOverflow = async () =>
|
|
assert.equal(
|
|
await page.evaluate(
|
|
() => document.documentElement.scrollWidth <= window.innerWidth,
|
|
),
|
|
true,
|
|
);
|
|
const formState = (form, state) =>
|
|
form
|
|
.locator('[data-form-state="' + state + '"]')
|
|
.waitFor({ state: 'visible' });
|
|
try {
|
|
for (const width of [1440, 390, 320]) {
|
|
await page.setViewportSize({ width, height: 1000 });
|
|
for (const theme of ['light', 'dark']) {
|
|
await page.emulateMedia({ colorScheme: theme });
|
|
await visit();
|
|
await noOverflow();
|
|
const nav = page.getByRole('navigation', {
|
|
name: 'Project settings',
|
|
exact: true,
|
|
});
|
|
assert.equal(
|
|
await nav
|
|
.getByRole('link', { name: 'General', exact: true })
|
|
.getAttribute('aria-current'),
|
|
'page',
|
|
);
|
|
assert.equal(
|
|
await nav
|
|
.getByRole('link', { name: 'Ticket settings', exact: true })
|
|
.getAttribute('href'),
|
|
'/projects/1/settings/metadata',
|
|
);
|
|
const sources = page.getByRole('list', { name: 'Setting sources' });
|
|
await sources.getByText('Inherited', { exact: true }).waitFor();
|
|
await sources
|
|
.getByText('Local override', { exact: true })
|
|
.first()
|
|
.waitFor();
|
|
assert.equal(await button('Switch template').isDisabled(), true);
|
|
assert.equal(await button('Delete project').isDisabled(), true);
|
|
if (width === 390 || width === 1440) {
|
|
await page.screenshot({
|
|
path: '/private/tmp/KAN-39-' + width + '-' + theme + '.png',
|
|
fullPage: true,
|
|
});
|
|
}
|
|
const trigger = button('Reset Project settings to template');
|
|
await trigger.focus();
|
|
await page.keyboard.press('Enter');
|
|
await dialog().waitFor();
|
|
await page.keyboard.press('Tab');
|
|
assert.equal(
|
|
await dialog().evaluate((element) =>
|
|
element.contains(document.activeElement),
|
|
),
|
|
true,
|
|
);
|
|
await noOverflow();
|
|
await page.keyboard.press('Escape');
|
|
await dialog().waitFor({ state: 'hidden' });
|
|
assert.equal(
|
|
await trigger.evaluate(
|
|
(element) => document.activeElement === element,
|
|
),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
mutations.length,
|
|
0,
|
|
'Opening and cancelling reset must not send a mutation',
|
|
);
|
|
}
|
|
}
|
|
await visit('?reset&noTemplate');
|
|
await page.getByText('No linked template', { exact: true }).waitFor();
|
|
assert.equal(await button('Reset Project settings to template').count(), 0);
|
|
await visit('?reset&baseline');
|
|
assert.equal(
|
|
await page.getByText('Local override', { exact: true }).count(),
|
|
0,
|
|
);
|
|
await visit('?reset&noAlternatives');
|
|
await page
|
|
.getByText('No other published templates', { exact: true })
|
|
.waitFor();
|
|
assert.equal(await button('Switch template').count(), 0);
|
|
|
|
await visit();
|
|
await button('Reset Ticket statuses to template').click();
|
|
await dialog().waitFor();
|
|
assert.equal(await confirmReset().isDisabled(), true);
|
|
const replacement = page.locator('#replacement-10');
|
|
await replacement.selectOption('1');
|
|
await result('validation');
|
|
const resetRequest = page.waitForRequest(
|
|
(request) => request.method() === 'POST',
|
|
);
|
|
await confirmReset().click();
|
|
assert.deepEqual((await resetRequest).postDataJSON(), {
|
|
areas: ['ticket_statuses'],
|
|
status_replacements: { 10: 1 },
|
|
});
|
|
await formState(dialog(), 'validation');
|
|
await page
|
|
.getByText('This area could not be reset.', { exact: true })
|
|
.waitFor();
|
|
assert.equal(await replacement.inputValue(), '1');
|
|
await result('server');
|
|
await confirmReset().click();
|
|
await formState(dialog(), 'server');
|
|
await result('success');
|
|
await dialog()
|
|
.getByRole('button', { name: 'Try again', exact: true })
|
|
.click();
|
|
await dialog().waitFor({ state: 'hidden' });
|
|
const sources = page.getByRole('list', { name: 'Setting sources' });
|
|
assert.equal(
|
|
await sources.getByText('Local override', { exact: true }).count(),
|
|
1,
|
|
'Resetting statuses preserves project settings override',
|
|
);
|
|
assert.equal(await button('Reset Ticket statuses to template').count(), 0);
|
|
await button('Reset Project settings to template').click();
|
|
await confirmReset().click();
|
|
await dialog().waitFor({ state: 'hidden' });
|
|
assert.equal(
|
|
await sources.getByText('Local override', { exact: true }).count(),
|
|
0,
|
|
);
|
|
assert.equal(await page.getByText('Enabled', { exact: true }).count(), 2);
|
|
|
|
await visit();
|
|
const switchForm = page
|
|
.locator('form')
|
|
.filter({ has: page.locator('#source_template_id') });
|
|
const select = page.locator('#source_template_id');
|
|
assert.equal(
|
|
await select.locator('option[value="1"]').count(),
|
|
0,
|
|
'Current template must not be selectable',
|
|
);
|
|
await select.selectOption('2');
|
|
await page.locator('#switch_confirmation_key').fill('core');
|
|
assert.equal(await button('Switch template').isDisabled(), true);
|
|
const countBefore = mutations.length;
|
|
await switchForm.evaluate((element) =>
|
|
element.dispatchEvent(
|
|
new Event('submit', { bubbles: true, cancelable: true }),
|
|
),
|
|
);
|
|
assert.equal(
|
|
mutations.length,
|
|
countBefore,
|
|
'Submit guard must reject a mismatching key',
|
|
);
|
|
await page.locator('#switch_confirmation_key').fill('CORE');
|
|
await result('validation');
|
|
await button('Switch template').click();
|
|
await formState(switchForm, 'validation');
|
|
assert.equal(
|
|
await page.locator('#switch_confirmation_key').inputValue(),
|
|
'CORE',
|
|
);
|
|
assert.equal(
|
|
await page
|
|
.locator('#switch_confirmation_key')
|
|
.getAttribute('aria-invalid'),
|
|
'true',
|
|
);
|
|
await page.route('**/switch-template', (route) => route.abort('failed'));
|
|
await button('Switch template').click();
|
|
await formState(switchForm, 'network');
|
|
await page.unroute('**/switch-template');
|
|
await result('success');
|
|
const switched = page.waitForRequest(
|
|
(request) => request.method() === 'POST',
|
|
);
|
|
await switchForm
|
|
.getByRole('button', { name: 'Try again', exact: true })
|
|
.click();
|
|
assert.deepEqual((await switched).postDataJSON(), {
|
|
confirmation_key: 'CORE',
|
|
source_template_id: 2,
|
|
});
|
|
await page.getByText('Delivery baseline', { exact: true }).waitFor();
|
|
assert.equal(
|
|
await page.locator('#switch_confirmation_key').inputValue(),
|
|
'',
|
|
);
|
|
assert.equal(await button('Switch template').isDisabled(), true);
|
|
|
|
await visit();
|
|
const deleteForm = page
|
|
.locator('form')
|
|
.filter({ has: page.locator('#confirmation_key') });
|
|
await page.locator('#confirmation_key').fill('core');
|
|
assert.equal(await button('Delete project').isDisabled(), true);
|
|
const beforeDelete = mutations.length;
|
|
await deleteForm.evaluate((element) =>
|
|
element.dispatchEvent(
|
|
new Event('submit', { bubbles: true, cancelable: true }),
|
|
),
|
|
);
|
|
assert.equal(mutations.length, beforeDelete);
|
|
await page.locator('#confirmation_key').fill('CORE');
|
|
await result('validation');
|
|
await button('Delete project').click();
|
|
await formState(deleteForm, 'validation');
|
|
assert.equal(
|
|
await page.locator('#confirmation_key').getAttribute('aria-invalid'),
|
|
'true',
|
|
);
|
|
assert.equal(await page.locator('#confirmation_key').inputValue(), 'CORE');
|
|
await result('success');
|
|
const deleted = page.waitForRequest(
|
|
(request) => request.method() === 'DELETE',
|
|
);
|
|
await button('Delete project').click();
|
|
const deletion = await deleted;
|
|
assert.equal(new URL(deletion.url()).pathname, '/projects/1');
|
|
assert.deepEqual(deletion.postDataJSON(), { confirmation_key: 'CORE' });
|
|
await page.getByRole('heading', { name: 'Destination reached' }).waitFor();
|
|
assert.deepEqual(errors, []);
|
|
console.log(
|
|
'Project general settings browser checks passed: setting sources, scoped resets, confirmations, replacements, errors, retries, keyboard, 320/390/1440px and both themes.',
|
|
);
|
|
} finally {
|
|
await browser.close();
|
|
}
|