|
| 1 | +/** |
| 2 | + * ObjectUI |
| 3 | + * Copyright (c) 2024-present ObjectStack Inc. |
| 4 | + * |
| 5 | + * This source code is licensed under the MIT license found in the |
| 6 | + * LICENSE file in the root directory of this source tree. |
| 7 | + */ |
| 8 | + |
| 9 | +/** |
| 10 | + * objectui#11591 — the pending-changes sheet's confirm note states what THAT |
| 11 | + * scope's Publish does. |
| 12 | + * |
| 13 | + * A package's sheet publishes through `POST /packages/:id/publish-drafts`, one |
| 14 | + * atomic pass, and its note says so. The package-less "Organization flows" |
| 15 | + * page (objectui#11553) has no package to publish as a batch: its Publish |
| 16 | + * promotes each draft by reference, one request per draft, and a draft that |
| 17 | + * fails stays pending while the others go live. The sheet there used to read |
| 18 | + * "Publishing releases the 1 pending draft of this package atomically." — a |
| 19 | + * package it does not have, and an atomicity it does not deliver. |
| 20 | + * |
| 21 | + * Rendered through a REAL `I18nProvider` over the shipped `en` pack, so the |
| 22 | + * sentence asserted is the one an author reads, not the call site's inline |
| 23 | + * `defaultValue`. That every locale's copy moved with `en` is pinned beside |
| 24 | + * the packs, in `packages/i18n/src/__tests__/confirmNoteSeparate-11591.test.ts`. |
| 25 | + */ |
| 26 | + |
| 27 | +import '@testing-library/jest-dom/vitest'; |
| 28 | +import * as React from 'react'; |
| 29 | +import { afterEach, describe, expect, it, vi } from 'vitest'; |
| 30 | +import { cleanup, render, screen, waitFor } from '@testing-library/react'; |
| 31 | +import { createI18n, I18nProvider } from '@object-ui/i18n'; |
| 32 | + |
| 33 | +import { DraftChangesPanel } from '../DraftChangesPanel'; |
| 34 | + |
| 35 | +afterEach(() => { |
| 36 | + cleanup(); |
| 37 | + vi.restoreAllMocks(); |
| 38 | +}); |
| 39 | + |
| 40 | +type Draft = { type: string; name: string; packageId: string | null }; |
| 41 | + |
| 42 | +/** Answer the `_drafts` feed with `drafts`, and every other read as absent. */ |
| 43 | +function serveDrafts(drafts: Draft[]) { |
| 44 | + global.fetch = vi.fn(async (input: RequestInfo | URL) => { |
| 45 | + const url = String(input); |
| 46 | + const ok = (body: unknown) => ({ ok: true, status: 200, json: async () => body }); |
| 47 | + if (url.includes('/_drafts')) return ok(drafts); |
| 48 | + if (/\/meta\/flow(\?|$)/.test(url)) return ok([]); |
| 49 | + return { ok: false, status: 404, json: async () => ({}) }; |
| 50 | + }) as unknown as typeof fetch; |
| 51 | +} |
| 52 | + |
| 53 | +function renderSheet(packageId: string | null) { |
| 54 | + return render( |
| 55 | + <I18nProvider |
| 56 | + instance={createI18n({ defaultLanguage: 'en', detectBrowserLanguage: false })} |
| 57 | + persistLanguage={false} |
| 58 | + > |
| 59 | + <DraftChangesPanel open onOpenChange={() => {}} packageId={packageId} onPublish={vi.fn()} /> |
| 60 | + </I18nProvider>, |
| 61 | + ); |
| 62 | +} |
| 63 | + |
| 64 | +/** The confirm footer's note — the paragraph right above the publish button. */ |
| 65 | +async function confirmNote(): Promise<string> { |
| 66 | + const button = await screen.findByTestId('draft-changes-publish', undefined, { timeout: 4000 }); |
| 67 | + const note = button.parentElement?.querySelector('p'); |
| 68 | + if (!note) throw new Error('the confirm footer rendered no note'); |
| 69 | + return note.textContent ?? ''; |
| 70 | +} |
| 71 | + |
| 72 | +describe('the confirm note says what this scope publishes (objectui#11591)', () => { |
| 73 | + it('package-less, one draft: no package, no atomicity', async () => { |
| 74 | + serveDrafts([{ type: 'flow', name: 'qa_urgent_alert_clone', packageId: null }]); |
| 75 | + renderSheet(null); |
| 76 | + |
| 77 | + const note = await confirmNote(); |
| 78 | + expect(note).toBe('Publishing releases the 1 pending draft on its own: if it fails, it stays pending.'); |
| 79 | + expect(note).not.toMatch(/atomic/i); |
| 80 | + expect(note).not.toMatch(/package/i); |
| 81 | + }); |
| 82 | + |
| 83 | + it('package-less, several drafts: each separately, and a partial publish is possible', async () => { |
| 84 | + serveDrafts([ |
| 85 | + { type: 'flow', name: 'qa_urgent_alert_clone', packageId: null }, |
| 86 | + { type: 'flow', name: 'qa_daily_digest_clone', packageId: null }, |
| 87 | + ]); |
| 88 | + renderSheet(null); |
| 89 | + |
| 90 | + const note = await confirmNote(); |
| 91 | + expect(note).toBe( |
| 92 | + 'Publishing releases the 2 pending drafts one at a time: a draft that fails stays pending while the others go live.', |
| 93 | + ); |
| 94 | + expect(note).not.toMatch(/atomic/i); |
| 95 | + expect(note).not.toMatch(/package/i); |
| 96 | + }); |
| 97 | + |
| 98 | + it('control — a package’s sheet keeps its sentence, byte for byte', async () => { |
| 99 | + serveDrafts([{ type: 'flow', name: 'showcase_daily_digest', packageId: 'com.example.showcase' }]); |
| 100 | + renderSheet('com.example.showcase'); |
| 101 | + |
| 102 | + await waitFor(async () => |
| 103 | + expect(await confirmNote()).toBe('Publishing releases the 1 pending draft of this package atomically.'), |
| 104 | + ); |
| 105 | + }); |
| 106 | + |
| 107 | + it('control — a package’s sheet, several drafts', async () => { |
| 108 | + serveDrafts([ |
| 109 | + { type: 'flow', name: 'showcase_daily_digest', packageId: 'com.example.showcase' }, |
| 110 | + { type: 'flow', name: 'showcase_urgent_task_alert', packageId: 'com.example.showcase' }, |
| 111 | + ]); |
| 112 | + renderSheet('com.example.showcase'); |
| 113 | + |
| 114 | + expect(await confirmNote()).toBe('Publishing releases all 2 pending drafts of this package atomically.'); |
| 115 | + }); |
| 116 | +}); |
0 commit comments