feat(KAN-32): add hierarchical acceptance criteria checklist
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AcceptanceCriteriaChecklistTest extends TestCase
|
||||
{
|
||||
public function test_checklist_preserves_hierarchy_identity_and_accessible_controls(): void
|
||||
{
|
||||
$process = new Process(['node', 'tests/Fixtures/acceptance-criteria.mjs'], base_path());
|
||||
$process->setTimeout(60);
|
||||
$process->run();
|
||||
|
||||
$this->assertTrue($process->isSuccessful(), $process->getOutput().$process->getErrorOutput());
|
||||
$this->assertStringContainsString('Acceptance criteria behavior checks passed.', $process->getOutput());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
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:4176';
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 900 },
|
||||
hasTouch: true,
|
||||
});
|
||||
const page = await context.newPage();
|
||||
page.setDefaultTimeout(10000);
|
||||
page.on('dialog', (dialog) => dialog.accept());
|
||||
const errors = [];
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
const checklist = () =>
|
||||
page
|
||||
.locator('form')
|
||||
.first()
|
||||
.getByRole('region', { name: 'Acceptance criteria' });
|
||||
const savedChecklist = () => page.locator('#ticket-checklist');
|
||||
const field = (number) =>
|
||||
page.getByRole('textbox', { name: `Criterion ${number}`, exact: true });
|
||||
const button = (name) => page.getByRole('button', { name, exact: true });
|
||||
const setResult = (value) =>
|
||||
context.addCookies([{ name: 'feedback_test_result', value, url: base }]);
|
||||
const waitState = (value) =>
|
||||
page.locator(`[data-form-state="${value}"]`).first().waitFor();
|
||||
const save = async () => {
|
||||
await button('Save changes').click();
|
||||
await waitState('success');
|
||||
};
|
||||
try {
|
||||
await page.goto(`${base}/projects/7/tickets/31`);
|
||||
await savedChecklist().waitFor();
|
||||
const boxes = savedChecklist().getByRole('checkbox');
|
||||
await setResult('server');
|
||||
await boxes.first().focus();
|
||||
await page.keyboard.press('Space');
|
||||
await page
|
||||
.getByText('Checklist status could not be saved. Please try again.')
|
||||
.waitFor();
|
||||
assert.equal(
|
||||
await boxes.first().isChecked(),
|
||||
false,
|
||||
'Failure must restore saved checkbox',
|
||||
);
|
||||
await setResult('success');
|
||||
let payload;
|
||||
page.on('request', (request) => {
|
||||
if (request.method() === 'PATCH') {
|
||||
payload = request.postDataJSON();
|
||||
}
|
||||
});
|
||||
await boxes.first().tap();
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
document.querySelectorAll('section[aria-busy="true"]').length === 0,
|
||||
);
|
||||
await savedChecklist().getByText('3 of 4 complete').waitFor();
|
||||
assert.deepEqual(
|
||||
payload.acceptance_criteria.map((item) => item.checked),
|
||||
[true, true, true, false],
|
||||
);
|
||||
assert.deepEqual(
|
||||
payload.acceptance_criteria.map((item) => item.id),
|
||||
[11, 12, 13, 14],
|
||||
);
|
||||
await page.locator('#ticket-edit > summary').click();
|
||||
await field(2).fill('Edited child');
|
||||
await field(2).press('Alt+ArrowLeft');
|
||||
assert.deepEqual(
|
||||
await checklist()
|
||||
.locator('li')
|
||||
.evaluateAll((items) =>
|
||||
items.map((item) => Number(item.dataset.indent)),
|
||||
),
|
||||
[0, 0, 1, 0],
|
||||
);
|
||||
await button('Indent criterion 2').tap();
|
||||
await button('Remove criterion 1').tap();
|
||||
assert.equal(await field(1).inputValue(), 'Edited child');
|
||||
assert.equal(
|
||||
await field(1).evaluate((el) => el === document.activeElement),
|
||||
true,
|
||||
);
|
||||
await button('Add criterion').focus();
|
||||
await page.keyboard.press('Enter');
|
||||
assert.equal(
|
||||
await field(4).evaluate((el) => el === document.activeElement),
|
||||
true,
|
||||
);
|
||||
await field(4).fill('New criterion');
|
||||
await button('Indent criterion 4').click();
|
||||
await setResult('validation');
|
||||
await button('Save changes').click();
|
||||
await waitState('validation');
|
||||
assert.equal(await field(1).inputValue(), 'Edited child');
|
||||
await setResult('success');
|
||||
await save();
|
||||
assert.deepEqual(
|
||||
payload.acceptance_criteria.map((item) => item.id),
|
||||
[12, 13, 14, undefined],
|
||||
);
|
||||
assert.deepEqual(
|
||||
payload.acceptance_criteria.map((item) => item.indent),
|
||||
[0, 1, 0, 1],
|
||||
);
|
||||
await page.reload();
|
||||
await page.locator('#ticket-edit > summary').click();
|
||||
assert.equal(await field(1).inputValue(), 'Edited child');
|
||||
assert.equal(await field(4).inputValue(), 'New criterion');
|
||||
// Blank/whitespace text cannot silently delete a persisted criterion.
|
||||
await field(1).fill(' ');
|
||||
assert.equal(await field(1).evaluate((el) => el.checkValidity()), false);
|
||||
const beforeInvalidSave = payload;
|
||||
await button('Save changes').click();
|
||||
assert.equal(payload, beforeInvalidSave);
|
||||
await button('Discard changes').click();
|
||||
assert.equal(await field(1).inputValue(), 'Edited child');
|
||||
assert.equal(await field(1).evaluate((el) => el.checkValidity()), true);
|
||||
for (const width of [1280, 390, 320]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
for (const theme of ['light', 'dark']) {
|
||||
await page.emulateMedia({ colorScheme: theme });
|
||||
await page.evaluate(
|
||||
(theme) =>
|
||||
document.documentElement.classList.toggle(
|
||||
'dark',
|
||||
theme === 'dark',
|
||||
),
|
||||
theme,
|
||||
);
|
||||
assert.equal(
|
||||
await checklist().evaluate(
|
||||
(el) => el.scrollWidth <= el.clientWidth,
|
||||
),
|
||||
true,
|
||||
);
|
||||
await checklist().screenshot({
|
||||
path: `/private/tmp/kan32-checklist-${width}-${theme}.png`,
|
||||
});
|
||||
await button('Outdent criterion 4').tap();
|
||||
await button('Indent criterion 4').tap();
|
||||
}
|
||||
}
|
||||
await page.goto(`${base}/projects/7/tickets/31?readonly=1`);
|
||||
await savedChecklist().waitFor();
|
||||
assert.equal(await savedChecklist().getByRole('textbox').count(), 0);
|
||||
assert.equal(await savedChecklist().getByRole('button').count(), 0);
|
||||
assert.equal(
|
||||
await savedChecklist().getByRole('checkbox').first().isDisabled(),
|
||||
true,
|
||||
);
|
||||
await page.goto(`${base}/projects/7/tickets/create`);
|
||||
await page.locator('#title').fill('Created with criteria');
|
||||
for (const text of ['First', 'Second']) {
|
||||
await button('Add criterion').click();
|
||||
await page.locator('textarea').last().fill(text);
|
||||
}
|
||||
await button('Indent criterion 2').tap();
|
||||
await checklist().getByRole('checkbox').first().focus();
|
||||
await page.keyboard.press('Space');
|
||||
const request = page.waitForRequest(
|
||||
(request) =>
|
||||
request.method() === 'POST' && request.url().endsWith('/tickets'),
|
||||
);
|
||||
await button('Create ticket').click();
|
||||
const created = (await request).postDataJSON();
|
||||
assert.deepEqual(created.acceptance_criteria, [
|
||||
{ text: 'First', checked: true, indent: 0 },
|
||||
{ text: 'Second', checked: true, indent: 1 },
|
||||
]);
|
||||
await page.getByRole('heading', { name: 'Destination' }).waitFor();
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'Acceptance criteria browser checks passed: persistence, errors, keyboard, touch, readonly, creation, mobile and themes.',
|
||||
);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
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,
|
||||
cacheDir: 'node_modules/.vite/acceptance-criteria-fixture',
|
||||
plugins: [vue()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('../../resources/js', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: { middlewareMode: true, hmr: false, watch: null },
|
||||
});
|
||||
try {
|
||||
const {
|
||||
canIndentAcceptanceCriterion: canIndent,
|
||||
indentAcceptanceCriterion: indent,
|
||||
removeAcceptanceCriterion: remove,
|
||||
syncAcceptanceCriteriaDescendantCheckedState: toggle,
|
||||
normalizeAcceptanceCriteria: normalize,
|
||||
} = await server.ssrLoadModule('/resources/js/lib/acceptance-criteria.ts');
|
||||
const items = [
|
||||
{ id: 11, text: 'Parent', indent: 0, checked: false },
|
||||
{ id: 12, text: 'Child', indent: 1, checked: true },
|
||||
{ id: 13, text: 'Grandchild', indent: 2, checked: false },
|
||||
{ id: 14, text: 'Next', indent: 0, checked: false },
|
||||
];
|
||||
const original = structuredClone(items);
|
||||
assert.equal(canIndent(items, 0), false);
|
||||
assert.equal(canIndent(items, 1), false);
|
||||
assert.equal(canIndent(items, 3), true);
|
||||
assert.deepEqual(
|
||||
indent(items, 1, -1).map((item) => item.indent),
|
||||
[0, 0, 1, 0],
|
||||
);
|
||||
assert.deepEqual(indent(indent(items, 1, -1), 1, 1), items);
|
||||
assert.deepEqual(indent(items, 0, -1), items);
|
||||
assert.deepEqual(indent(items, -1, 1), items);
|
||||
assert.deepEqual(remove(items, 0), [
|
||||
{ ...items[1], indent: 0 },
|
||||
{ ...items[2], indent: 1 },
|
||||
items[3],
|
||||
]);
|
||||
assert.deepEqual(remove(items, 1), [
|
||||
items[0],
|
||||
{ ...items[2], indent: 1 },
|
||||
items[3],
|
||||
]);
|
||||
assert.deepEqual(remove(items, 99), items);
|
||||
assert.deepEqual(
|
||||
toggle(items, 0, true).map((item) => item.checked),
|
||||
[true, true, true, false],
|
||||
);
|
||||
assert.deepEqual(
|
||||
toggle(items, 1, false).map((item) => item.checked),
|
||||
[false, false, false, false],
|
||||
);
|
||||
const deep = Array.from({ length: 9 }, (_, indent) => ({
|
||||
text: 'Deep',
|
||||
checked: false,
|
||||
indent,
|
||||
}));
|
||||
assert.equal(
|
||||
canIndent([{ text: 'Before', indent: 0, checked: false }, ...deep], 1),
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(
|
||||
normalize(remove(items, 0)).map((item) => item.id),
|
||||
[12, 13, 14],
|
||||
);
|
||||
assert.deepEqual(items, original, 'Editing must not mutate saved criteria');
|
||||
const { default: Checklist } = await server.ssrLoadModule(
|
||||
'/resources/js/components/tickets/AcceptanceCriteriaChecklist.vue',
|
||||
);
|
||||
const { createLocalization } = await server.ssrLoadModule(
|
||||
'/resources/js/lib/localization.ts',
|
||||
);
|
||||
const render = (props) =>
|
||||
renderToString(
|
||||
createSSRApp({ render: () => h(Checklist, props) }).use(
|
||||
createLocalization(),
|
||||
),
|
||||
);
|
||||
const editable = await render({ modelValue: items });
|
||||
assert.equal((editable.match(/<textarea/g) ?? []).length, 4);
|
||||
assert.match(editable, /Indent criterion 4/);
|
||||
assert.match(editable, /1 of 4 complete/);
|
||||
const readonly = await render({ modelValue: items, readonly: true });
|
||||
assert.doesNotMatch(readonly, /<textarea|Add criterion|Edit checklist/);
|
||||
assert.equal((readonly.match(/type="checkbox" disabled/g) ?? []).length, 4);
|
||||
assert.match(
|
||||
await render({ modelValue: [], error: 'Server validation' }),
|
||||
/Server validation/,
|
||||
);
|
||||
console.log('Acceptance criteria behavior checks passed.');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
@@ -320,15 +320,18 @@ try {
|
||||
await page.goto(`${base}/projects/7/tickets/create`);
|
||||
await page.locator('#title').fill('New ticket draft');
|
||||
await page.locator('#description').fill('Keep this description');
|
||||
await page.getByRole('button', { name: 'Add criterion' }).click();
|
||||
await page
|
||||
.locator('#acceptance_criteria_checklist')
|
||||
.fill('[ ] Keep this checklist');
|
||||
.getByRole('textbox', { name: 'Criterion 1', exact: true })
|
||||
.fill('Keep this checklist');
|
||||
await setResult('validation');
|
||||
await page.locator('form button[type="submit"]').first().click();
|
||||
await state('validation');
|
||||
assert.equal(
|
||||
await page.locator('#acceptance_criteria_checklist').inputValue(),
|
||||
'[ ] Keep this checklist',
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Criterion 1', exact: true })
|
||||
.inputValue(),
|
||||
'Keep this checklist',
|
||||
);
|
||||
await page.route('**/projects/7/tickets', (route) => route.abort('failed'));
|
||||
await page.locator('form button[type="submit"]').first().click();
|
||||
@@ -349,8 +352,10 @@ try {
|
||||
await page.locator('#title').waitFor();
|
||||
assert.equal(await page.locator('#title').inputValue(), 'New ticket draft');
|
||||
assert.equal(
|
||||
await page.locator('#acceptance_criteria_checklist').inputValue(),
|
||||
'[ ] Keep this checklist',
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Criterion 1', exact: true })
|
||||
.inputValue(),
|
||||
'Keep this checklist',
|
||||
);
|
||||
await setResult('success');
|
||||
await page.locator('form button[type="submit"]').first().click();
|
||||
@@ -359,9 +364,10 @@ try {
|
||||
await page.goto(`${base}/projects/7/tickets/31`);
|
||||
await page.locator('#ticket-edit > summary').click();
|
||||
await page.locator('#title').fill('Updated ticket draft');
|
||||
await page.getByRole('button', { name: 'Add criterion' }).click();
|
||||
await page
|
||||
.locator('#acceptance_criteria_checklist')
|
||||
.fill('[ ] Edited checklist');
|
||||
.getByRole('textbox', { name: 'Criterion 1', exact: true })
|
||||
.fill('Edited checklist');
|
||||
await setResult('server');
|
||||
await page.locator('form button[type="submit"]').first().click();
|
||||
await state('server');
|
||||
@@ -370,8 +376,10 @@ try {
|
||||
'Updated ticket draft',
|
||||
);
|
||||
assert.equal(
|
||||
await page.locator('#acceptance_criteria_checklist').inputValue(),
|
||||
'[ ] Edited checklist',
|
||||
await page
|
||||
.getByRole('textbox', { name: 'Criterion 1', exact: true })
|
||||
.inputValue(),
|
||||
'Edited checklist',
|
||||
);
|
||||
await setResult('success');
|
||||
await page.getByRole('button', { name: 'Try again' }).click();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import process from 'node:process';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import tailwind from '@tailwindcss/vite';
|
||||
import { createServer } from 'vite';
|
||||
@@ -36,7 +37,19 @@ let ticket = {
|
||||
ticket_status: status,
|
||||
is_blocked: false,
|
||||
story_points: 0,
|
||||
acceptance_criteria: [],
|
||||
acceptance_criteria: process.env.CRITERIA_FIXTURE
|
||||
? [
|
||||
{ id: 11, text: 'Parent criterion', indent: 0, checked: false },
|
||||
{ id: 12, text: 'Nested criterion', indent: 1, checked: true },
|
||||
{ id: 13, text: 'Deep criterion', indent: 2, checked: false },
|
||||
{
|
||||
id: 14,
|
||||
text: 'Independent criterion',
|
||||
indent: 0,
|
||||
checked: false,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
field_values: [],
|
||||
worklog_entries: [],
|
||||
created_at: '2026-09-10T12:00:00Z',
|
||||
@@ -125,7 +138,7 @@ server.middlewares.use(async (request, response, next) => {
|
||||
ticketStatusOptions: [status],
|
||||
users: [],
|
||||
parentTicketOptions: [],
|
||||
canEditTicket: true,
|
||||
canEditTicket: !url.searchParams.has('readonly'),
|
||||
canManageProject: true,
|
||||
childTicketTree: [],
|
||||
linkedTickets: [],
|
||||
|
||||
@@ -105,22 +105,30 @@ try {
|
||||
await page.keyboard.press('Enter');
|
||||
await page.getByLabel('Title', { exact: true }).fill('Unsaved title draft');
|
||||
await page
|
||||
.getByRole('checkbox', { name: 'Cache requests safely', exact: true })
|
||||
.check();
|
||||
.locator('#ticket-checklist')
|
||||
.getByRole('checkbox', {
|
||||
name: 'Complete criterion 1: Cache requests safely',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector('#acceptance-criteria-11').checked,
|
||||
() =>
|
||||
document.querySelectorAll(
|
||||
'#ticket-checklist input[type=checkbox]',
|
||||
)[1].checked,
|
||||
);
|
||||
assert.equal(
|
||||
await page.getByLabel('Title', { exact: true }).inputValue(),
|
||||
'Unsaved title draft',
|
||||
);
|
||||
await page
|
||||
.getByLabel('Acceptance criteria checklist', { exact: true })
|
||||
.fill('[ ] A draft checklist item');
|
||||
.getByLabel('Criterion 1', { exact: true })
|
||||
.fill('A draft checklist item');
|
||||
assert.equal(
|
||||
await page
|
||||
.locator('#ticket-checklist')
|
||||
.getByRole('checkbox', {
|
||||
name: 'Cache requests safely',
|
||||
name: 'Complete criterion 1: Cache requests safely',
|
||||
exact: true,
|
||||
})
|
||||
.isDisabled(),
|
||||
@@ -155,10 +163,8 @@ try {
|
||||
'Unsaved title draft',
|
||||
);
|
||||
assert.equal(
|
||||
await page
|
||||
.getByLabel('Acceptance criteria checklist', { exact: true })
|
||||
.inputValue(),
|
||||
'[ ] A draft checklist item',
|
||||
await page.getByLabel('Criterion 1', { exact: true }).inputValue(),
|
||||
'A draft checklist item',
|
||||
);
|
||||
await visit('?reject');
|
||||
await edit().click();
|
||||
@@ -196,16 +202,21 @@ try {
|
||||
await page.getByText('Ticket updated.', { exact: true }).waitFor();
|
||||
await visit('?rejectToggle');
|
||||
await page
|
||||
.getByRole('checkbox', { name: 'Cache requests safely', exact: true })
|
||||
.check();
|
||||
.locator('#ticket-checklist')
|
||||
.getByRole('checkbox', {
|
||||
name: 'Complete criterion 1: Cache requests safely',
|
||||
exact: true,
|
||||
})
|
||||
.click();
|
||||
await page
|
||||
.getByRole('alert')
|
||||
.filter({ hasText: 'Checklist status could not be saved' })
|
||||
.waitFor();
|
||||
assert.equal(
|
||||
await page
|
||||
.locator('#ticket-checklist')
|
||||
.getByRole('checkbox', {
|
||||
name: 'Cache requests safely',
|
||||
name: 'Complete criterion 1: Cache requests safely',
|
||||
exact: true,
|
||||
})
|
||||
.isChecked(),
|
||||
|
||||
@@ -478,7 +478,6 @@ if (serve) {
|
||||
'story_points',
|
||||
'parent_ticket_id',
|
||||
'description',
|
||||
'acceptance_criteria_checklist',
|
||||
'is_blocked',
|
||||
'blocked_comment',
|
||||
'field-1',
|
||||
@@ -489,6 +488,8 @@ if (serve) {
|
||||
`Missing editor: ${field}`,
|
||||
);
|
||||
}
|
||||
assert.match(html, /aria-label="Criterion 1"/);
|
||||
assert.match(html, /Add criterion/);
|
||||
assert.match(html, /Create branch/);
|
||||
assert.match(html, /Save link/);
|
||||
assert.match(html, /Remove/);
|
||||
@@ -505,7 +506,7 @@ if (serve) {
|
||||
const empty = await render('?empty');
|
||||
for (const text of [
|
||||
'No description has been added yet.',
|
||||
'No acceptance criteria have been added yet.',
|
||||
'No acceptance criteria yet.',
|
||||
'No status',
|
||||
'Unassigned',
|
||||
'No author',
|
||||
@@ -540,6 +541,10 @@ if (serve) {
|
||||
assert.match(unavailable, /Ticket creation needs at least one type/);
|
||||
for (const creating of [false, true]) {
|
||||
const invalid = await render('?rejected&allErrors', creating);
|
||||
assert.match(
|
||||
invalid,
|
||||
/aria-describedby="[^"]+-hint [^"]+-error"|No acceptance criteria yet/,
|
||||
);
|
||||
for (const id of [
|
||||
'title',
|
||||
'description',
|
||||
@@ -548,7 +553,6 @@ if (serve) {
|
||||
'assignee_user_id',
|
||||
'parent_ticket_id',
|
||||
'story_points',
|
||||
'acceptance_criteria_checklist',
|
||||
]) {
|
||||
assert.ok(invalid.includes(`id="${id}-error"`));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ page.setDefaultTimeout(10000);
|
||||
const base = 'http://127.0.0.1:4185/projects/7/tickets';
|
||||
const errors = [];
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
const field = (label) => page.getByLabel(label, { exact: true });
|
||||
const field = (label) => form().getByLabel(label, { exact: true });
|
||||
const form = () => page.locator('form').first();
|
||||
const state = (value) =>
|
||||
form().locator(`[data-form-state="${value}"]`).waitFor();
|
||||
@@ -29,9 +29,19 @@ async function fill(create) {
|
||||
await field('Assignee').selectOption('2');
|
||||
await field('Parent ticket').selectOption('40');
|
||||
await field('Estimated story points').fill('0');
|
||||
await field('Acceptance criteria checklist').fill(
|
||||
'[ ] Main criterion\n [x] Nested criterion',
|
||||
);
|
||||
if (create) {
|
||||
await button('Add criterion').click();
|
||||
await button('Add criterion').click();
|
||||
await button('Indent criterion 2').click();
|
||||
}
|
||||
await field('Criterion 1').fill('Main criterion');
|
||||
await field('Criterion 2').fill('Nested criterion');
|
||||
await form()
|
||||
.getByRole('checkbox', {
|
||||
name: 'Complete criterion 2: Nested criterion',
|
||||
exact: true,
|
||||
})
|
||||
.check();
|
||||
if (create) {
|
||||
await field('Status').selectOption('4');
|
||||
} else {
|
||||
@@ -154,7 +164,7 @@ try {
|
||||
'Assignee',
|
||||
'Parent ticket',
|
||||
'Estimated story points',
|
||||
'Acceptance criteria checklist',
|
||||
'Criterion 1',
|
||||
...(create
|
||||
? ['Status']
|
||||
: ['Author', 'Blocker comment', 'Impact score', 'Reviewer']),
|
||||
@@ -231,12 +241,12 @@ try {
|
||||
.waitFor();
|
||||
if (create) {
|
||||
await page.goBack();
|
||||
await page
|
||||
.getByRole('heading', { name: 'Create ticket', exact: true })
|
||||
.waitFor();
|
||||
await field('Title').waitFor();
|
||||
assert.equal(await field('Title').inputValue(), '');
|
||||
assert.equal(
|
||||
await field('Acceptance criteria checklist').inputValue(),
|
||||
'',
|
||||
);
|
||||
assert.equal(await field('Criterion 1').count(), 0);
|
||||
} else {
|
||||
await form()
|
||||
.getByText('All changes saved', { exact: true })
|
||||
|
||||
Reference in New Issue
Block a user