Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/pin-advisory-sink-seam-6969.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
---

Test-only change: pins the metadata advisory sink seam (`useMetadataClient` ->
`createConsoleMetadataClient` -> `MetadataClient`), so an advisory the server
returns on a save or a publish is asserted to actually reach the toast. No
published behaviour changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* The advisory chain's MIDDLE link: an emitted event actually REACHES the sink
* (objectui#6969).
*
* ## What was missing, and why nothing red said so
*
* The chain has three links, and before this file only its two ends were
* pinned:
*
* producer MetadataClient.save / .publishDraft emit the event
* -> pinned by metadata-client.saveAdvisories.test.ts and
* metadata-client.publishAdvisories.test.ts
* MIDDLE useMetadataClient hands its toast sink to the client factory,
* which hands it to the MetadataClient constructor
* -> pinned by NOTHING
* renderer emitSaveAdvisories turns an event into the warning
* -> pinned by saveAdvisoryToast.test.ts
*
* The producer suites assert the event is EMITTED, into a sink they construct
* themselves. The renderer suite asserts the message is BUILT, over an event it
* writes by hand. Neither one ever asserts that the thing emitted and the thing
* rendered are connected — so cutting the middle silenced BOTH doors, save and
* publish, with every named suite green. Measured before writing this file:
* with the `onSaveAdvisory` hand-off deleted from the factory call in
* `useMetadata.ts`, 226 test files / 2369 tests stayed green.
*
* The mechanism of the blind spot is worth naming, because it is not an
* oversight anyone could have spotted by reading a coverage number: 51 suites
* `vi.mock('./useMetadata')` — legitimately, they are testing pages, not
* plumbing — and, before this file, exactly ZERO imported the real
* `useMetadataClient`. The seam was mocked away everywhere it appeared.
*
* ## What is real here, and what is not
*
* Real: the hook, its `useCallback` sink, the i18n `t` it closes over, the
* PreviewModeContext read, `createConsoleMetadataClient`, the authenticated
* fetch wrapper, the `MetadataClient` instance, its response parsing, its
* `readSaveAdvisories` filter, and `emitSaveAdvisories`.
*
* Stubbed, and only these two:
* - `sonner` — the terminal sink. It is imported directly by the hook (not
* injected), so intercepting the module is the only way to observe what
* arrives. Everything BETWEEN the producer and it is the real thing, which
* is the whole point.
* - `globalThis.fetch` — the server. It answers with the response body the
* framework's runtime authoring gate actually sends.
*
* ## Scope — deliberately NOT a bigger test at either end
*
* This asserts the CONNECTION and nothing else. The tier (warning, never
* error), the per-finding formatting and the empty-list drop are the renderer
* suite's; `withEnvironment` clone survival and the response-shape filtering
* are the producer suites'. Duplicating any of them here would grow the suite
* without closing the hole this file exists to close.
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, cleanup } from '@testing-library/react';

// The one stub in the middle of the chain — see the module doc. `toast` is
// imported by `useMetadata.ts` as a module binding, so it cannot be handed over
// the way `saveAdvisoryToast.test.ts` hands over its sink.
vi.mock('sonner', () => ({
toast: {
warning: vi.fn(),
error: vi.fn(),
success: vi.fn(),
info: vi.fn(),
message: vi.fn(),
},
}));

import { toast } from 'sonner';
import { useMetadataClient } from './useMetadata';

/**
* The measured `nightly_purge` finding, in the gate's D3 shape. All six keys
* are required — `readSaveAdvisories` drops anything half-shaped, so a fixture
* missing one would make this file pass for the wrong reason.
*/
const PURGE_ADVISORY = {
severity: 'warning' as const,
rule: 'flow/delete-without-filter',
where: 'flow "nightly_purge" · node "purge old rows"',
path: 'flows[0].nodes[2].config.filters',
message: 'this delete_record node sets multi: true with no filter, so it deletes every row',
hint: 'add a filter, or set multi: false to delete a single record',
};

/** A committed write the gate had something to say about. */
const ADVISED_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
advisories: [PURGE_ADVISORY],
};

/** The same write, clean. The server omits `advisories` entirely. */
const CLEAN_BODY = {
success: true,
version: 'sha256:0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0',
seq: 7,
};

function jsonResponse(body: unknown): Response {
return new Response(JSON.stringify(body), {
status: 200,
headers: { 'content-type': 'application/json' },
});
}

let fetchMock: ReturnType<typeof vi.fn>;

function serverAnswers(body: unknown) {
fetchMock = vi.fn(async () => jsonResponse(body));
vi.stubGlobal('fetch', fetchMock);
}

beforeEach(() => {
vi.mocked(toast.warning).mockClear();
serverAnswers(ADVISED_BODY);
});

afterEach(() => {
vi.unstubAllGlobals();
cleanup();
});

/** The `(title, options)` pair the sink was called with. */
function warningCall(): [string, { description?: string; duration?: number } | undefined] {
const calls = vi.mocked(toast.warning).mock.calls;
expect(calls).toHaveLength(1);
return calls[0] as [string, { description?: string; duration?: number } | undefined];
}

describe('useMetadataClient — an emitted advisory reaches the toast sink', () => {
it('SAVE door: a save whose response carries findings renders them', async () => {
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });

const [title, options] = warningCall();
// The verb proves the DOOR discriminator survived the whole seam: it is set
// by the producer and read by the renderer, and nothing in between may lose
// or rewrite it.
expect(title).toMatch(/^Saved\b/);
// The finding itself arrived — not merely "something was toasted".
expect(options?.description).toContain(PURGE_ADVISORY.message);
expect(options?.description).toContain(PURGE_ADVISORY.rule);
});

it('PUBLISH door: the same client, the same sink, the published verb', async () => {
const { result } = renderHook(() => useMetadataClient());

// `publishDraft` rather than `publish` because it is what the console's own
// publish path (`usePublishAllDrafts`) calls through THIS hook.
await result.current.publishDraft('flow', 'nightly_purge');

const [title, options] = warningCall();
expect(title).toMatch(/^Published\b/);
expect(options?.description).toContain(PURGE_ADVISORY.message);
});

it('CONTROL: a clean write says nothing at either door', async () => {
serverAnswers(CLEAN_BODY);
const { result } = renderHook(() => useMetadataClient());

await result.current.save('flow', 'nightly_purge', { name: 'nightly_purge' });
await result.current.publishDraft('flow', 'nightly_purge');

// The zero above is only a reading beside a control that MUST hit: both
// writes really did travel the chain, so "the sink stayed quiet" is about
// the empty advisory list and not about a chain that never ran.
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(toast.warning).not.toHaveBeenCalled();
});
});
Loading