Skip to content

Commit 96335bc

Browse files
backspaceclaude
andcommitted
Add matrix test for re-publishing flow
Covers the gap that let CS-11043 ship: every existing publish-realm test does a single publish, so a republish that signals success but serves stale content has no regression net. The new test defines a sentinel card, publishes, asserts the initial sentinel renders on the published URL, edits the source, republishes, and asserts the updated sentinel renders (and the initial one is gone). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a09b038 commit 96335bc

1 file changed

Lines changed: 164 additions & 0 deletions

File tree

packages/matrix/tests/publish-realm.spec.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,170 @@ test.describe('Publish realm', () => {
239239
).toBeVisible();
240240
});
241241

242+
test('re-publishing reflects updated source content on the published URL (CS-11043)', async ({
243+
page,
244+
}) => {
245+
// CS-11043 regression net. The bug was: a republish reported success
246+
// server-side but the published URL kept serving the previous publish's
247+
// rendered HTML, sometimes for tens of hours. Every existing
248+
// publish-realm test does exactly one publish — this is the gap the
249+
// bug slipped through. Here we publish, change content, publish
250+
// again, and assert the published URL shows the new content (and not
251+
// the old).
252+
253+
await clearLocalStorage(page, serverIndexUrl);
254+
user = await createSubscribedUserAndLogin(
255+
page,
256+
'publish-realm',
257+
serverIndexUrl,
258+
);
259+
260+
let serverURL = new URL(serverIndexUrl);
261+
let defaultRealmURL = `${serverURL.protocol}//${serverURL.host}/${user.username}/new-workspace/`;
262+
263+
await createRealm(page, 'new-workspace', '1New Workspace');
264+
265+
// Define a card type whose isolated template renders a single
266+
// sentinel string we can grep for in the published HTML.
267+
await postCardSource(
268+
page,
269+
defaultRealmURL,
270+
'sentinel-card.gts',
271+
`
272+
import { CardDef, Component, field, contains } from "https://cardstack.com/base/card-api";
273+
import StringField from "https://cardstack.com/base/string";
274+
275+
export class SentinelCard extends CardDef {
276+
@field value = contains(StringField);
277+
278+
static isolated = class extends Component<typeof this> {
279+
<template>
280+
<div data-test-sentinel-output>{{@model.value}}</div>
281+
</template>
282+
};
283+
}
284+
`,
285+
);
286+
287+
// Initial index.json: an instance of SentinelCard carrying the
288+
// sentinel that we expect the first publish to render.
289+
let initialSentinel = `sentinel-initial-${Date.now()}`;
290+
await postCardSource(
291+
page,
292+
defaultRealmURL,
293+
'index.json',
294+
JSON.stringify(
295+
{
296+
data: {
297+
type: 'card',
298+
attributes: { value: initialSentinel },
299+
meta: {
300+
adoptsFrom: { module: './sentinel-card', name: 'SentinelCard' },
301+
},
302+
},
303+
},
304+
null,
305+
2,
306+
),
307+
);
308+
309+
// Open the publish modal and do the first publish.
310+
await page.locator('[data-test-workspace="1New Workspace"]').click();
311+
await page.locator('[data-test-submode-switcher] button').click();
312+
await page.locator('[data-test-boxel-menu-item-text="Host"]').click();
313+
await page.locator('[data-test-publish-realm-button]').click();
314+
await page.locator('[data-test-default-domain-checkbox]').click();
315+
await page.locator('[data-test-publish-button]').click();
316+
await page.waitForSelector('[data-test-unpublish-button]');
317+
318+
// Open the published URL and verify the initial sentinel renders.
319+
let firstTabPromise = page.waitForEvent('popup');
320+
await page
321+
.locator(
322+
'[data-test-publish-realm-modal] [data-test-open-boxel-space-button]',
323+
)
324+
.click();
325+
let firstTab = await firstTabPromise;
326+
await firstTab.waitForLoadState();
327+
await expect(firstTab.locator('[data-test-sentinel-output]')).toHaveText(
328+
initialSentinel,
329+
{ timeout: 30_000 },
330+
);
331+
await firstTab.close();
332+
await page.bringToFront();
333+
334+
// Close the modal so we can re-open it cleanly for the second publish.
335+
await page.locator('[data-test-close-modal]').click();
336+
337+
// Change the index card's sentinel value. This is the "user edits
338+
// their realm between publishes" step.
339+
let updatedSentinel = `sentinel-updated-${Date.now()}`;
340+
await postCardSource(
341+
page,
342+
defaultRealmURL,
343+
'index.json',
344+
JSON.stringify(
345+
{
346+
data: {
347+
type: 'card',
348+
attributes: { value: updatedSentinel },
349+
meta: {
350+
adoptsFrom: { module: './sentinel-card', name: 'SentinelCard' },
351+
},
352+
},
353+
},
354+
null,
355+
2,
356+
),
357+
);
358+
359+
// Re-open the publish modal. The default-domain checkbox is still
360+
// there (the realm appears as already-published in the modal); the
361+
// publish button is what we re-click to push the new content.
362+
await page.locator('[data-test-publish-realm-button]').click();
363+
// The publish handler awaits sourceRealm.indexing() before doing the
364+
// copy, so we don't need a manual settle here — the click below
365+
// serializes behind any pending incremental indexing on the source.
366+
await page.locator('[data-test-default-domain-checkbox]').click();
367+
let publishButton = page.locator('[data-test-publish-button]');
368+
// Set up the network wait BEFORE clicking — the handler awaits the
369+
// full reindex before returning 202, so the response is the
370+
// authoritative "publish is done" signal. 60s budget covers the
371+
// from-scratch reindex even on slow CI.
372+
let publishResponsePromise = page.waitForResponse(
373+
(r) =>
374+
r.url().endsWith('/_publish-realm') && r.request().method() === 'POST',
375+
{ timeout: 60_000 },
376+
);
377+
await publishButton.click();
378+
let publishResponse = await publishResponsePromise;
379+
expect(
380+
publishResponse.status(),
381+
'second publish should succeed',
382+
).toBeLessThan(300);
383+
384+
// Open the published URL again and verify the UPDATED sentinel
385+
// renders — and the initial sentinel does NOT. This is the
386+
// assertion CS-11043 would have failed: the old test only checked
387+
// for "card visible", which stays true even when serving stale
388+
// content.
389+
let secondTabPromise = page.waitForEvent('popup');
390+
await page
391+
.locator(
392+
'[data-test-publish-realm-modal] [data-test-open-boxel-space-button]',
393+
)
394+
.click();
395+
let secondTab = await secondTabPromise;
396+
await secondTab.waitForLoadState();
397+
await expect(secondTab.locator('[data-test-sentinel-output]')).toHaveText(
398+
updatedSentinel,
399+
{ timeout: 30_000 },
400+
);
401+
await expect(secondTab.locator('body')).not.toContainText(initialSentinel);
402+
await secondTab.close();
403+
await page.bringToFront();
404+
});
405+
242406
test('open site popover opens with shift-click', async ({ page }) => {
243407
await publishDefaultRealm(page);
244408

0 commit comments

Comments
 (0)