feat: article editor core
This commit is contained in:
1 parent
d493a181e1
commit
a4734b2c91
55 files changed
+4186
-496
No files matched your search
@@ -153,6 +153,56 @@ const verifiedSession: ApiMock = {
|
||||
body: { is_verified: true, username: 'marta' },
|
||||
}
|
||||
|
||||
const editorNow = '2026-09-26T11:02:00Z'
|
||||
|
||||
const bathroomBody =
|
||||
"<p>Every bathroom is different, but the way we price them isn't. We start with a visit, measure everything, and <strong>write down what stays</strong>.</p>" +
|
||||
'<h2>What goes into the price</h2>' +
|
||||
'<ul><li>Stripping out and making good</li><li>Plumbing moves, if any</li><li>Tiles, fittings and a day for silicone</li></ul>' +
|
||||
'<p>We don’t add a margin on materials you choose yourself.</p>'
|
||||
|
||||
const editorArticle = {
|
||||
id: 12,
|
||||
title: 'How we price a bathroom renovation',
|
||||
body: bathroomBody,
|
||||
images: [
|
||||
{ id: 1, url: '/images/1.png' },
|
||||
{ id: 2, url: '/images/2.png' },
|
||||
{ id: 3, url: '/images/3.png' },
|
||||
],
|
||||
is_online: false,
|
||||
created_at: '2026-03-12T09:14:00Z',
|
||||
updated_at: editorNow,
|
||||
}
|
||||
|
||||
function editorApi(overrides: Record<string, ApiMock> = {}): Record<string, ApiMock> {
|
||||
return {
|
||||
'GET /v1/auth/me': verifiedSession,
|
||||
'GET /v1/article/12': { status: 200, body: editorArticle },
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
async function focusBoldSample(page: Page) {
|
||||
await page.getByText('write down what stays').click()
|
||||
}
|
||||
|
||||
async function markDirty(page: Page) {
|
||||
await page.getByText('choose yourself').click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.type('x')
|
||||
await page.keyboard.press('Backspace')
|
||||
}
|
||||
|
||||
async function typeExactBodyLength(page: Page, totalLength: number) {
|
||||
const editable = page.locator('[contenteditable="true"]')
|
||||
await editable.click()
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.keyboard.press('Delete')
|
||||
const inner = 'a'.repeat(Math.max(0, totalLength - 7))
|
||||
await page.keyboard.insertText(inner)
|
||||
}
|
||||
|
||||
export const visualStates: Record<string, Record<string, VisualState>> = {
|
||||
home: {
|
||||
Default: {
|
||||
@@ -319,6 +369,119 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
editor: {
|
||||
Loading: {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi({
|
||||
'GET /v1/article/12': { status: 200, body: editorArticle, hang: true },
|
||||
}),
|
||||
},
|
||||
Saved: {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
steps: async (page) => {
|
||||
await focusBoldSample(page)
|
||||
},
|
||||
},
|
||||
'Unsaved changes': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
steps: async (page) => {
|
||||
await markDirty(page)
|
||||
await focusBoldSample(page)
|
||||
},
|
||||
},
|
||||
Saving: {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi({
|
||||
'PATCH /v1/article/12': { status: 200, body: editorArticle, hang: true },
|
||||
}),
|
||||
steps: async (page) => {
|
||||
await markDirty(page)
|
||||
await focusBoldSample(page)
|
||||
await page.getByRole('button', { name: 'Save', exact: true }).click()
|
||||
},
|
||||
},
|
||||
'Save failed': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi({ 'PATCH /v1/article/12': { status: 500 } }),
|
||||
steps: async (page) => {
|
||||
await markDirty(page)
|
||||
await page.getByRole('button', { name: 'Save', exact: true }).click()
|
||||
await page.getByText('Save failed').waitFor()
|
||||
await focusBoldSample(page)
|
||||
},
|
||||
},
|
||||
'Near the limit': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
steps: async (page) => {
|
||||
await typeExactBodyLength(page, 9240)
|
||||
},
|
||||
},
|
||||
'Too long': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
steps: async (page) => {
|
||||
await typeExactBodyLength(page, 10214)
|
||||
},
|
||||
},
|
||||
Preview: {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
steps: async (page) => {
|
||||
const preview = page.getByRole('switch')
|
||||
if (await preview.count()) {
|
||||
await preview.click()
|
||||
}
|
||||
},
|
||||
},
|
||||
'Leave with unsaved changes': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
paused: true,
|
||||
steps: async (page) => {
|
||||
await markDirty(page)
|
||||
await focusBoldSample(page)
|
||||
await page.getByRole('button', { name: 'Back to Articles' }).click()
|
||||
await page.getByRole('alertdialog').waitFor()
|
||||
},
|
||||
},
|
||||
Offline: {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
steps: async (page) => {
|
||||
await markDirty(page)
|
||||
await focusBoldSample(page)
|
||||
await page.evaluate(() => {
|
||||
Object.defineProperty(navigator, 'onLine', {
|
||||
configurable: true,
|
||||
get: () => false,
|
||||
})
|
||||
window.dispatchEvent(new Event('offline'))
|
||||
})
|
||||
await page.getByText('Waiting for connection').waitFor()
|
||||
},
|
||||
},
|
||||
'Not found (404)': {
|
||||
path: '/article/9999/edit',
|
||||
now: editorNow,
|
||||
api: {
|
||||
'GET /v1/auth/me': verifiedSession,
|
||||
'GET /v1/article/9999': { status: 404 },
|
||||
},
|
||||
},
|
||||
},
|
||||
verify: {
|
||||
Verifying: {
|
||||
path: '/register/validate?c=abc123',
|
||||
|
||||
Reference in new issue
Block a user