feat(KAN-41): edit allowed status transitions
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
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:4188';
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
page.setDefaultTimeout(10000);
|
||||
const errors = [];
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
const editor = page.getByRole('region', { name: 'Status moves', exact: true });
|
||||
const source = () => editor.getByLabel('From status', { exact: true });
|
||||
const check = (name) =>
|
||||
editor.getByRole('checkbox', { name: new RegExp(name) });
|
||||
const button = (name) => editor.getByRole('button', { name, exact: true });
|
||||
const state = (name) => editor.locator('[data-form-state="' + name + '"]');
|
||||
const result = (value) =>
|
||||
context.addCookies([{ name: 'workflow_test_result', value, url: base }]);
|
||||
const visit = async (query = '?reset') => {
|
||||
await result('success');
|
||||
await page.goto(base + '/projects/1/settings/board' + query);
|
||||
await source().waitFor();
|
||||
};
|
||||
const noOverflow = async () =>
|
||||
assert.equal(
|
||||
await page.evaluate(
|
||||
() => document.documentElement.scrollWidth <= window.innerWidth,
|
||||
),
|
||||
true,
|
||||
);
|
||||
const save = async (id, targets) => {
|
||||
const sent = page.waitForRequest((request) => request.method() === 'PATCH');
|
||||
await button('Save moves').click();
|
||||
const request = await sent;
|
||||
assert.equal(
|
||||
new URL(request.url()).pathname,
|
||||
'/projects/1/settings/board/statuses/' + id + '/transitions',
|
||||
);
|
||||
assert.deepEqual(request.postDataJSON(), {
|
||||
allowed_ticket_status_ids: targets,
|
||||
});
|
||||
};
|
||||
try {
|
||||
for (const width of [1440, 768, 390, 320]) {
|
||||
await page.setViewportSize({ width, height: 1000 });
|
||||
for (const theme of ['light', 'dark']) {
|
||||
await page.emulateMedia({ colorScheme: theme });
|
||||
await visit();
|
||||
assert.equal(await check('Ready').isChecked(), true);
|
||||
assert.equal(await check('Building').isChecked(), false);
|
||||
assert.equal(await check('Backlog').count(), 0);
|
||||
assert.equal(await button('Save moves').isDisabled(), true);
|
||||
await check('Building').focus();
|
||||
await page.keyboard.press('Space');
|
||||
assert.equal(await check('Building').isChecked(), true);
|
||||
await editor
|
||||
.getByText('Unsaved changes', { exact: true })
|
||||
.waitFor();
|
||||
await noOverflow();
|
||||
await button('Reset changes').click();
|
||||
assert.equal(await check('Building').isChecked(), false);
|
||||
assert.equal(await check('Ready').isChecked(), true);
|
||||
if ([1440, 390].includes(width)) {
|
||||
await editor.scrollIntoViewIfNeeded();
|
||||
await page.screenshot({
|
||||
path: '/private/tmp/KAN-41-' + width + '-' + theme + '.png',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await visit();
|
||||
await check('Ready').uncheck();
|
||||
await check('Review').check();
|
||||
await result('validation');
|
||||
await save(1, [4]);
|
||||
await state('validation').waitFor();
|
||||
await editor
|
||||
.getByText('Choose a destination from this project.', { exact: true })
|
||||
.waitFor();
|
||||
assert.equal(await check('Review').isChecked(), true);
|
||||
assert.equal(await check('Ready').isChecked(), false);
|
||||
assert.equal(
|
||||
await editor.getByRole('group').getAttribute('aria-invalid'),
|
||||
'true',
|
||||
);
|
||||
await result('server');
|
||||
await save(1, [4]);
|
||||
await state('server').waitFor();
|
||||
await result('success');
|
||||
await button('Try again').click();
|
||||
await state('success').waitFor();
|
||||
assert.equal(await button('Save moves').isDisabled(), true);
|
||||
assert.equal(await check('Review').isChecked(), true);
|
||||
await page.reload();
|
||||
await source().waitFor();
|
||||
assert.equal(await check('Review').isChecked(), true);
|
||||
assert.equal(await check('Ready').isChecked(), false);
|
||||
await source().selectOption('4');
|
||||
assert.equal(await check('Backlog').isChecked(), false);
|
||||
assert.equal(await check('Review').count(), 0);
|
||||
await check('Backlog').check();
|
||||
page.once('dialog', (prompt) => prompt.dismiss());
|
||||
await source().selectOption('1');
|
||||
assert.equal(await source().inputValue(), '4');
|
||||
assert.equal(await check('Backlog').isChecked(), true);
|
||||
page.once('dialog', (prompt) => prompt.accept());
|
||||
await source().selectOption('1');
|
||||
assert.equal(await source().inputValue(), '1');
|
||||
assert.equal(await check('Review').isChecked(), true);
|
||||
await check('Review').uncheck();
|
||||
await editor.getByText(/With no destinations selected/).waitFor();
|
||||
await save(1, []);
|
||||
await state('success').waitFor();
|
||||
await page.reload();
|
||||
await source().waitFor();
|
||||
assert.equal(await editor.locator('input:checked').count(), 0);
|
||||
await editor.getByText('Saved destinations: 0', { exact: true }).waitFor();
|
||||
// A disconnected request retains the draft and supports a single retry.
|
||||
await check('Building').check();
|
||||
await page.route('**/statuses/1/transitions', (route) =>
|
||||
route.abort('failed'),
|
||||
);
|
||||
await save(1, [3]);
|
||||
await state('network').waitFor();
|
||||
assert.equal(await check('Building').isChecked(), true);
|
||||
await page.unroute('**/statuses/1/transitions');
|
||||
await button('Try again').click();
|
||||
await state('success').waitFor();
|
||||
// Pending saves disable inputs, source changes and duplicate submits.
|
||||
let release;
|
||||
const blocked = new Promise((resolve) => {
|
||||
release = resolve;
|
||||
});
|
||||
await page.route('**/statuses/1/transitions', async (route) => {
|
||||
await blocked;
|
||||
await route.continue();
|
||||
});
|
||||
await check('Review').check();
|
||||
await save(1, [3, 4]);
|
||||
await state('loading').waitFor();
|
||||
assert.equal(await source().isDisabled(), true);
|
||||
assert.equal(await check('Building').isDisabled(), true);
|
||||
assert.equal(
|
||||
await editor.locator('button[type=submit]').isDisabled(),
|
||||
true,
|
||||
);
|
||||
release();
|
||||
await state('success').waitFor();
|
||||
await page.unroute('**/statuses/1/transitions');
|
||||
await visit('?reset&empty');
|
||||
await editor
|
||||
.getByText('No other statuses exist yet.', { exact: true })
|
||||
.waitFor();
|
||||
assert.equal(await editor.getByRole('checkbox').count(), 0);
|
||||
await noOverflow();
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'Transition browser checks passed: directed rules, persisted selection, clearing all targets, reset, source-change protection, validation/network/server errors, retry, pending saves, keyboard, responsive widths and both themes.',
|
||||
);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
Reference in New Issue
Block a user