@operato/layout 10.23.0 → 10.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/coverage/lcov-report/index.html +70 -70
- package/coverage/lcov.info +7270 -6178
- package/dist/src/actions/entity360-registry.d.ts +69 -0
- package/dist/src/actions/entity360-registry.js +96 -0
- package/dist/src/actions/entity360-registry.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/test/entity360-registry.test.d.ts +1 -0
- package/dist/test/entity360-registry.test.js +61 -0
- package/dist/test/entity360-registry.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/test/entity360-registry.test.ts +84 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type TemplateResult } from 'lit';
|
|
2
|
+
/**
|
|
3
|
+
* Which kinds of thing open a 360, and what each module puts in it (ADR-0067, the person 360 ruling).
|
|
4
|
+
*
|
|
5
|
+
* An entity's name is a link to its 360 (ADR-0067 decision 1), and a grid shows names by the dozen -
|
|
6
|
+
* `updater` alone is a person column on 65 framework screens. Writing an opener into every column
|
|
7
|
+
* would never finish. Instead a kind is declared once with the GraphQL `__typename`s it claims, and
|
|
8
|
+
* whatever draws a value can ask `entity360KindOf(value)`: registered, it is a link; not, it stays
|
|
9
|
+
* text, exactly as before.
|
|
10
|
+
*
|
|
11
|
+
* This file knows no product words and imports no product module. A kind brings its own title; each
|
|
12
|
+
* module hangs its own section with its own read door (`registerEntity360Section`), the way activity
|
|
13
|
+
* panels hang on the activity detail. Opening a kind with no section says so in one line - never a
|
|
14
|
+
* blank dock.
|
|
15
|
+
*/
|
|
16
|
+
/** The thing a 360 is about: its kind, its id, and the value it was opened from. */
|
|
17
|
+
export interface Entity360Subject {
|
|
18
|
+
kind: string;
|
|
19
|
+
id: string;
|
|
20
|
+
value: Record<string, any>;
|
|
21
|
+
}
|
|
22
|
+
export interface Entity360KindDeclaration {
|
|
23
|
+
/** The name sections register under - 'person', 'role', … */
|
|
24
|
+
kind: string;
|
|
25
|
+
/** The GraphQL `__typename`s whose values open this kind. A typename belongs to one kind. */
|
|
26
|
+
typenames: string[];
|
|
27
|
+
/** The dock's title for a subject - what a person recognises it by. */
|
|
28
|
+
title(subject: Entity360Subject): string;
|
|
29
|
+
/** A line under the title, if the kind has one. */
|
|
30
|
+
context?(subject: Entity360Subject): string;
|
|
31
|
+
}
|
|
32
|
+
export interface Entity360Section {
|
|
33
|
+
/** Unique within its kind. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Lower comes first; equal orders keep the order they were registered in. */
|
|
36
|
+
order?: number;
|
|
37
|
+
/** The section's content - usually the module's own element, which reads its own door. */
|
|
38
|
+
render(subject: Entity360Subject): TemplateResult;
|
|
39
|
+
}
|
|
40
|
+
export type Entity360Rejection = {
|
|
41
|
+
kind: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
reason: string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Declare a kind. A kind named twice, or a typename another kind already claims, is refused and
|
|
47
|
+
* kept in `entity360Rejections()` - silently taking the second would reroute links nobody asked to move.
|
|
48
|
+
*/
|
|
49
|
+
export declare function registerEntity360Kind(declaration: Entity360KindDeclaration): void;
|
|
50
|
+
/** Hang a section on a kind. A name already on that kind is refused. */
|
|
51
|
+
export declare function registerEntity360Section(kind: string, section: Entity360Section): void;
|
|
52
|
+
/**
|
|
53
|
+
* The kind a value opens, or `undefined`.
|
|
54
|
+
*
|
|
55
|
+
* `undefined` for anything not registered and for a value without `__typename` (a call that turned
|
|
56
|
+
* the cache off) - the caller draws it as it always has.
|
|
57
|
+
*/
|
|
58
|
+
export declare function entity360KindOf(value: unknown): string | undefined;
|
|
59
|
+
/** The sections hung on a kind, in order. */
|
|
60
|
+
export declare function entity360Sections(kind: string): Entity360Section[];
|
|
61
|
+
/** What was refused, and why - for a test or a boot check to read. */
|
|
62
|
+
export declare function entity360Rejections(): Entity360Rejection[];
|
|
63
|
+
/**
|
|
64
|
+
* Open the 360 of a value.
|
|
65
|
+
*
|
|
66
|
+
* A kind with sections shows them in order. A kind with none - or a value that opens no kind -
|
|
67
|
+
* shows one line saying there is no 360 for it yet. The dock is never blank.
|
|
68
|
+
*/
|
|
69
|
+
export declare function openEntity360For(value: Record<string, any>, returnFocus?: () => void): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { i18next } from '@operato/i18n';
|
|
3
|
+
import { showEntity360, hideEntity360, canGoBackEntity360 } from './entity360.js';
|
|
4
|
+
const kinds = new Map();
|
|
5
|
+
const kindOfTypename = new Map();
|
|
6
|
+
const sections = new Map();
|
|
7
|
+
const rejected = [];
|
|
8
|
+
let registered = 0;
|
|
9
|
+
/** The dock has one owner for everything opened through this registry. */
|
|
10
|
+
const OWNER = {};
|
|
11
|
+
/**
|
|
12
|
+
* Declare a kind. A kind named twice, or a typename another kind already claims, is refused and
|
|
13
|
+
* kept in `entity360Rejections()` - silently taking the second would reroute links nobody asked to move.
|
|
14
|
+
*/
|
|
15
|
+
export function registerEntity360Kind(declaration) {
|
|
16
|
+
var _a;
|
|
17
|
+
const kind = String((_a = declaration === null || declaration === void 0 ? void 0 : declaration.kind) !== null && _a !== void 0 ? _a : '').trim();
|
|
18
|
+
if (!kind)
|
|
19
|
+
return void rejected.push({ kind: '(no kind)', reason: 'no-kind' });
|
|
20
|
+
if (kinds.has(kind))
|
|
21
|
+
return void rejected.push({ kind, reason: 'duplicate-kind' });
|
|
22
|
+
if (typeof declaration.title !== 'function')
|
|
23
|
+
return void rejected.push({ kind, reason: 'no-title' });
|
|
24
|
+
const taken = (declaration.typenames || []).find(typename => kindOfTypename.has(typename));
|
|
25
|
+
if (taken)
|
|
26
|
+
return void rejected.push({ kind, name: taken, reason: 'typename-claimed' });
|
|
27
|
+
kinds.set(kind, { ...declaration, kind });
|
|
28
|
+
for (const typename of declaration.typenames || [])
|
|
29
|
+
kindOfTypename.set(typename, kind);
|
|
30
|
+
}
|
|
31
|
+
/** Hang a section on a kind. A name already on that kind is refused. */
|
|
32
|
+
export function registerEntity360Section(kind, section) {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const name = String((_a = section === null || section === void 0 ? void 0 : section.name) !== null && _a !== void 0 ? _a : '').trim();
|
|
35
|
+
if (!name)
|
|
36
|
+
return void rejected.push({ kind, reason: 'no-name' });
|
|
37
|
+
if (typeof section.render !== 'function')
|
|
38
|
+
return void rejected.push({ kind, name, reason: 'no-render' });
|
|
39
|
+
const list = (_b = sections.get(kind)) !== null && _b !== void 0 ? _b : [];
|
|
40
|
+
if (list.some(one => one.name === name))
|
|
41
|
+
return void rejected.push({ kind, name, reason: 'duplicate-name' });
|
|
42
|
+
list.push({ ...section, name, at: registered++ });
|
|
43
|
+
list.sort((a, b) => { var _a, _b; return ((_a = a.order) !== null && _a !== void 0 ? _a : 0) - ((_b = b.order) !== null && _b !== void 0 ? _b : 0) || a.at - b.at; });
|
|
44
|
+
sections.set(kind, list);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The kind a value opens, or `undefined`.
|
|
48
|
+
*
|
|
49
|
+
* `undefined` for anything not registered and for a value without `__typename` (a call that turned
|
|
50
|
+
* the cache off) - the caller draws it as it always has.
|
|
51
|
+
*/
|
|
52
|
+
export function entity360KindOf(value) {
|
|
53
|
+
const typename = value === null || value === void 0 ? void 0 : value.__typename;
|
|
54
|
+
return typeof typename === 'string' ? kindOfTypename.get(typename) : undefined;
|
|
55
|
+
}
|
|
56
|
+
/** The sections hung on a kind, in order. */
|
|
57
|
+
export function entity360Sections(kind) {
|
|
58
|
+
var _a;
|
|
59
|
+
return [...((_a = sections.get(kind)) !== null && _a !== void 0 ? _a : [])];
|
|
60
|
+
}
|
|
61
|
+
/** What was refused, and why - for a test or a boot check to read. */
|
|
62
|
+
export function entity360Rejections() {
|
|
63
|
+
return [...rejected];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Open the 360 of a value.
|
|
67
|
+
*
|
|
68
|
+
* A kind with sections shows them in order. A kind with none - or a value that opens no kind -
|
|
69
|
+
* shows one line saying there is no 360 for it yet. The dock is never blank.
|
|
70
|
+
*/
|
|
71
|
+
export function openEntity360For(value, returnFocus) {
|
|
72
|
+
var _a, _b, _c, _d;
|
|
73
|
+
const kind = (_a = entity360KindOf(value)) !== null && _a !== void 0 ? _a : '';
|
|
74
|
+
const subject = { kind, id: String((_b = value === null || value === void 0 ? void 0 : value.id) !== null && _b !== void 0 ? _b : ''), value: value !== null && value !== void 0 ? value : {} };
|
|
75
|
+
const declaration = kinds.get(kind);
|
|
76
|
+
const hung = entity360Sections(kind);
|
|
77
|
+
const content = hung.length
|
|
78
|
+
? html `${hung.map(section => section.render(subject))}`
|
|
79
|
+
: html `<p data-not-yet>${i18next.t('text.entity360-not-yet', { defaultValue: 'there is no 360 for this kind yet.' })}</p>`;
|
|
80
|
+
showEntity360(OWNER, {
|
|
81
|
+
key: `${kind}:${subject.id}`,
|
|
82
|
+
title: declaration ? declaration.title(subject) : String((_d = (_c = value === null || value === void 0 ? void 0 : value.name) !== null && _c !== void 0 ? _c : value === null || value === void 0 ? void 0 : value.id) !== null && _d !== void 0 ? _d : ''),
|
|
83
|
+
context: (declaration === null || declaration === void 0 ? void 0 : declaration.context) ? declaration.context(subject) : '',
|
|
84
|
+
content,
|
|
85
|
+
labels: {
|
|
86
|
+
close: i18next.t('button.close', { defaultValue: 'close' }),
|
|
87
|
+
expand: i18next.t('button.expand', { defaultValue: 'expand' }),
|
|
88
|
+
collapse: i18next.t('button.collapse', { defaultValue: 'collapse' }),
|
|
89
|
+
resize: i18next.t('label.resize', { defaultValue: 'resize' }),
|
|
90
|
+
back: i18next.t('button.back', { defaultValue: 'back' })
|
|
91
|
+
},
|
|
92
|
+
onClose: () => hideEntity360(OWNER),
|
|
93
|
+
onBack: canGoBackEntity360() ? () => history.back() : undefined
|
|
94
|
+
}, returnFocus);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=entity360-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity360-registry.js","sourceRoot":"","sources":["../../../src/actions/entity360-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, type TemplateResult } from 'lit'\nimport { i18next } from '@operato/i18n'\n\nimport { showEntity360, hideEntity360, canGoBackEntity360 } from './entity360.js'\n\n/**\n * Which kinds of thing open a 360, and what each module puts in it (ADR-0067, the person 360 ruling).\n *\n * An entity's name is a link to its 360 (ADR-0067 decision 1), and a grid shows names by the dozen -\n * `updater` alone is a person column on 65 framework screens. Writing an opener into every column\n * would never finish. Instead a kind is declared once with the GraphQL `__typename`s it claims, and\n * whatever draws a value can ask `entity360KindOf(value)`: registered, it is a link; not, it stays\n * text, exactly as before.\n *\n * This file knows no product words and imports no product module. A kind brings its own title; each\n * module hangs its own section with its own read door (`registerEntity360Section`), the way activity\n * panels hang on the activity detail. Opening a kind with no section says so in one line - never a\n * blank dock.\n */\n\n/** The thing a 360 is about: its kind, its id, and the value it was opened from. */\nexport interface Entity360Subject {\n kind: string\n id: string\n value: Record<string, any>\n}\n\nexport interface Entity360KindDeclaration {\n /** The name sections register under - 'person', 'role', … */\n kind: string\n /** The GraphQL `__typename`s whose values open this kind. A typename belongs to one kind. */\n typenames: string[]\n /** The dock's title for a subject - what a person recognises it by. */\n title(subject: Entity360Subject): string\n /** A line under the title, if the kind has one. */\n context?(subject: Entity360Subject): string\n}\n\nexport interface Entity360Section {\n /** Unique within its kind. */\n name: string\n /** Lower comes first; equal orders keep the order they were registered in. */\n order?: number\n /** The section's content - usually the module's own element, which reads its own door. */\n render(subject: Entity360Subject): TemplateResult\n}\n\nexport type Entity360Rejection = { kind: string; name?: string; reason: string }\n\nconst kinds = new Map<string, Entity360KindDeclaration>()\nconst kindOfTypename = new Map<string, string>()\nconst sections = new Map<string, (Entity360Section & { at: number })[]>()\nconst rejected: Entity360Rejection[] = []\nlet registered = 0\n\n/** The dock has one owner for everything opened through this registry. */\nconst OWNER = {}\n\n/**\n * Declare a kind. A kind named twice, or a typename another kind already claims, is refused and\n * kept in `entity360Rejections()` - silently taking the second would reroute links nobody asked to move.\n */\nexport function registerEntity360Kind(declaration: Entity360KindDeclaration): void {\n const kind = String(declaration?.kind ?? '').trim()\n\n if (!kind) return void rejected.push({ kind: '(no kind)', reason: 'no-kind' })\n if (kinds.has(kind)) return void rejected.push({ kind, reason: 'duplicate-kind' })\n if (typeof declaration.title !== 'function') return void rejected.push({ kind, reason: 'no-title' })\n\n const taken = (declaration.typenames || []).find(typename => kindOfTypename.has(typename))\n if (taken) return void rejected.push({ kind, name: taken, reason: 'typename-claimed' })\n\n kinds.set(kind, { ...declaration, kind })\n for (const typename of declaration.typenames || []) kindOfTypename.set(typename, kind)\n}\n\n/** Hang a section on a kind. A name already on that kind is refused. */\nexport function registerEntity360Section(kind: string, section: Entity360Section): void {\n const name = String(section?.name ?? '').trim()\n\n if (!name) return void rejected.push({ kind, reason: 'no-name' })\n if (typeof section.render !== 'function') return void rejected.push({ kind, name, reason: 'no-render' })\n\n const list = sections.get(kind) ?? []\n if (list.some(one => one.name === name)) return void rejected.push({ kind, name, reason: 'duplicate-name' })\n\n list.push({ ...section, name, at: registered++ })\n list.sort((a, b) => (a.order ?? 0) - (b.order ?? 0) || a.at - b.at)\n sections.set(kind, list)\n}\n\n/**\n * The kind a value opens, or `undefined`.\n *\n * `undefined` for anything not registered and for a value without `__typename` (a call that turned\n * the cache off) - the caller draws it as it always has.\n */\nexport function entity360KindOf(value: unknown): string | undefined {\n const typename = (value as any)?.__typename\n return typeof typename === 'string' ? kindOfTypename.get(typename) : undefined\n}\n\n/** The sections hung on a kind, in order. */\nexport function entity360Sections(kind: string): Entity360Section[] {\n return [...(sections.get(kind) ?? [])]\n}\n\n/** What was refused, and why - for a test or a boot check to read. */\nexport function entity360Rejections(): Entity360Rejection[] {\n return [...rejected]\n}\n\n/**\n * Open the 360 of a value.\n *\n * A kind with sections shows them in order. A kind with none - or a value that opens no kind -\n * shows one line saying there is no 360 for it yet. The dock is never blank.\n */\nexport function openEntity360For(value: Record<string, any>, returnFocus?: () => void): void {\n const kind = entity360KindOf(value) ?? ''\n const subject: Entity360Subject = { kind, id: String(value?.id ?? ''), value: value ?? {} }\n const declaration = kinds.get(kind)\n const hung = entity360Sections(kind)\n\n const content = hung.length\n ? html`${hung.map(section => section.render(subject))}`\n : html`<p data-not-yet>${i18next.t('text.entity360-not-yet', { defaultValue: 'there is no 360 for this kind yet.' })}</p>`\n\n showEntity360(\n OWNER,\n {\n key: `${kind}:${subject.id}`,\n title: declaration ? declaration.title(subject) : String(value?.name ?? value?.id ?? ''),\n context: declaration?.context ? declaration.context(subject) : '',\n content,\n labels: {\n close: i18next.t('button.close', { defaultValue: 'close' }),\n expand: i18next.t('button.expand', { defaultValue: 'expand' }),\n collapse: i18next.t('button.collapse', { defaultValue: 'collapse' }),\n resize: i18next.t('label.resize', { defaultValue: 'resize' }),\n back: i18next.t('button.back', { defaultValue: 'back' })\n },\n onClose: () => hideEntity360(OWNER),\n onBack: canGoBackEntity360() ? () => history.back() : undefined\n },\n returnFocus\n )\n}\n"]}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -11,4 +11,5 @@ export * from './actions/snackbar.js';
|
|
|
11
11
|
export * from './components/ox-split-pane.js';
|
|
12
12
|
export * from './components/ox-entity360-dock.js';
|
|
13
13
|
export * from './actions/entity360.js';
|
|
14
|
+
export * from './actions/entity360-registry.js';
|
|
14
15
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAA;AAIzB,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAE/C,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA","sourcesContent":["import './initializer.js'\n\nexport { PopupHandle, PopupOptions } from '@operato/popup'\n\nexport * from './layouts/ox-snack-bar.js'\nexport * from './layouts/ox-header-bar.js'\nexport * from './layouts/ox-nav-bar.js'\nexport * from './layouts/ox-aside-bar.js'\nexport * from './layouts/ox-footer-bar.js'\nexport * from './layouts/ox-page-header-bar.js'\nexport * from './layouts/ox-page-footer-bar.js'\n\nexport * from './actions/layout.js'\nexport * from './actions/snackbar.js'\n\nexport * from './components/ox-split-pane.js'\nexport * from './components/ox-entity360-dock.js'\nexport * from './actions/entity360.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAA;AAIzB,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,yBAAyB,CAAA;AACvC,cAAc,2BAA2B,CAAA;AACzC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,iCAAiC,CAAA;AAC/C,cAAc,iCAAiC,CAAA;AAE/C,cAAc,qBAAqB,CAAA;AACnC,cAAc,uBAAuB,CAAA;AAErC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,mCAAmC,CAAA;AACjD,cAAc,wBAAwB,CAAA;AACtC,cAAc,iCAAiC,CAAA","sourcesContent":["import './initializer.js'\n\nexport { PopupHandle, PopupOptions } from '@operato/popup'\n\nexport * from './layouts/ox-snack-bar.js'\nexport * from './layouts/ox-header-bar.js'\nexport * from './layouts/ox-nav-bar.js'\nexport * from './layouts/ox-aside-bar.js'\nexport * from './layouts/ox-footer-bar.js'\nexport * from './layouts/ox-page-header-bar.js'\nexport * from './layouts/ox-page-footer-bar.js'\n\nexport * from './actions/layout.js'\nexport * from './actions/snackbar.js'\n\nexport * from './components/ox-split-pane.js'\nexport * from './components/ox-entity360-dock.js'\nexport * from './actions/entity360.js'\nexport * from './actions/entity360-registry.js'\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '../src/index.js';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
import '../src/index.js';
|
|
4
|
+
import { entity360KindOf, entity360Rejections, entity360Sections, hideEntity360, openEntity360For, registerEntity360Kind, registerEntity360Section } from '../src/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* The 360 registry (ADR-0067, the person 360 ruling): a kind claims GraphQL typenames, modules hang
|
|
7
|
+
* sections on it, and anything drawing a value asks whether it opens a 360. The registry lives for
|
|
8
|
+
* the page, so every test uses kinds and typenames of its own.
|
|
9
|
+
*/
|
|
10
|
+
const settle = () => new Promise(resolve => setTimeout(resolve, 50));
|
|
11
|
+
let bar;
|
|
12
|
+
const dock = () => bar.shadowRoot.querySelector('ox-entity360-dock');
|
|
13
|
+
describe('entity 360 registry', () => {
|
|
14
|
+
beforeEach(async () => {
|
|
15
|
+
bar = await fixture(html `<ox-aside-bar></ox-aside-bar>`);
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => { var _a, _b; return (_b = (_a = dock()) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.onClose(); });
|
|
18
|
+
it('a value opens the kind its typename was claimed by', () => {
|
|
19
|
+
registerEntity360Kind({ kind: 'k1', typenames: ['T1'], title: s => s.value.name });
|
|
20
|
+
expect(entity360KindOf({ __typename: 'T1', id: '1' })).to.equal('k1');
|
|
21
|
+
});
|
|
22
|
+
it('★ an unregistered typename, or a value without one, opens nothing - drawn as before', () => {
|
|
23
|
+
expect(entity360KindOf({ __typename: 'NobodyClaimsThis', id: '1' })).to.equal(undefined);
|
|
24
|
+
expect(entity360KindOf({ id: '1', name: 'no typename' })).to.equal(undefined);
|
|
25
|
+
expect(entity360KindOf(null)).to.equal(undefined);
|
|
26
|
+
});
|
|
27
|
+
it('★ opens the dock with the sections in order, each given the subject', async () => {
|
|
28
|
+
registerEntity360Kind({ kind: 'k2', typenames: ['T2'], title: s => `title ${s.value.name}`, context: s => `id ${s.id}` });
|
|
29
|
+
registerEntity360Section('k2', { name: 'later', order: 2, render: s => html `<p data-section="later">${s.id}</p>` });
|
|
30
|
+
registerEntity360Section('k2', { name: 'first', order: 1, render: s => html `<p data-section="first">${s.kind}</p>` });
|
|
31
|
+
openEntity360For({ __typename: 'T2', id: '42', name: 'Kim' });
|
|
32
|
+
await settle();
|
|
33
|
+
expect(dock().view.title).to.equal('title Kim');
|
|
34
|
+
expect(dock().view.context).to.equal('id 42');
|
|
35
|
+
expect(entity360Sections('k2').map(section => section.name)).to.deep.equal(['first', 'later']);
|
|
36
|
+
});
|
|
37
|
+
it('★ a kind with no section says so in one line - never a blank dock', async () => {
|
|
38
|
+
registerEntity360Kind({ kind: 'k3', typenames: ['T3'], title: s => s.value.name });
|
|
39
|
+
openEntity360For({ __typename: 'T3', id: '7', name: 'Lee' });
|
|
40
|
+
await settle();
|
|
41
|
+
const content = await fixture(html `<div>${dock().view.content}</div>`);
|
|
42
|
+
expect(content.querySelector('[data-not-yet]')).to.exist;
|
|
43
|
+
});
|
|
44
|
+
it('★ refuses a second kind of the same name and a typename already claimed', () => {
|
|
45
|
+
registerEntity360Kind({ kind: 'k4', typenames: ['T4'], title: () => '' });
|
|
46
|
+
registerEntity360Kind({ kind: 'k4', typenames: ['T4b'], title: () => '' });
|
|
47
|
+
registerEntity360Kind({ kind: 'k5', typenames: ['T4'], title: () => '' });
|
|
48
|
+
const reasons = entity360Rejections().filter(one => one.kind === 'k4' || one.kind === 'k5').map(one => one.reason);
|
|
49
|
+
expect(reasons).to.include('duplicate-kind');
|
|
50
|
+
expect(reasons).to.include('typename-claimed');
|
|
51
|
+
expect(entity360KindOf({ __typename: 'T4' })).to.equal('k4');
|
|
52
|
+
});
|
|
53
|
+
it('refuses a section name used twice on one kind', () => {
|
|
54
|
+
registerEntity360Section('k6', { name: 'same', render: () => html `` });
|
|
55
|
+
registerEntity360Section('k6', { name: 'same', render: () => html `` });
|
|
56
|
+
expect(entity360Sections('k6').length).to.equal(1);
|
|
57
|
+
expect(entity360Rejections().some(one => one.kind === 'k6' && one.reason === 'duplicate-name')).to.equal(true);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
void hideEntity360;
|
|
61
|
+
//# sourceMappingURL=entity360-registry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity360-registry.test.js","sourceRoot":"","sources":["../../test/entity360-registry.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\nimport '../src/index.js'\nimport {\n entity360KindOf,\n entity360Rejections,\n entity360Sections,\n hideEntity360,\n openEntity360For,\n registerEntity360Kind,\n registerEntity360Section,\n type OxEntity360Dock\n} from '../src/index.js'\n\n/**\n * The 360 registry (ADR-0067, the person 360 ruling): a kind claims GraphQL typenames, modules hang\n * sections on it, and anything drawing a value asks whether it opens a 360. The registry lives for\n * the page, so every test uses kinds and typenames of its own.\n */\n\nconst settle = () => new Promise(resolve => setTimeout(resolve, 50))\nlet bar: HTMLElement\nconst dock = () => bar.shadowRoot!.querySelector('ox-entity360-dock') as OxEntity360Dock\n\ndescribe('entity 360 registry', () => {\n beforeEach(async () => {\n bar = await fixture(html`<ox-aside-bar></ox-aside-bar>`)\n })\n afterEach(() => dock()?.view?.onClose())\n\n it('a value opens the kind its typename was claimed by', () => {\n registerEntity360Kind({ kind: 'k1', typenames: ['T1'], title: s => s.value.name })\n expect(entity360KindOf({ __typename: 'T1', id: '1' })).to.equal('k1')\n })\n\n it('★ an unregistered typename, or a value without one, opens nothing - drawn as before', () => {\n expect(entity360KindOf({ __typename: 'NobodyClaimsThis', id: '1' })).to.equal(undefined)\n expect(entity360KindOf({ id: '1', name: 'no typename' })).to.equal(undefined)\n expect(entity360KindOf(null)).to.equal(undefined)\n })\n\n it('★ opens the dock with the sections in order, each given the subject', async () => {\n registerEntity360Kind({ kind: 'k2', typenames: ['T2'], title: s => `title ${s.value.name}`, context: s => `id ${s.id}` })\n registerEntity360Section('k2', { name: 'later', order: 2, render: s => html`<p data-section=\"later\">${s.id}</p>` })\n registerEntity360Section('k2', { name: 'first', order: 1, render: s => html`<p data-section=\"first\">${s.kind}</p>` })\n\n openEntity360For({ __typename: 'T2', id: '42', name: 'Kim' })\n await settle()\n\n expect(dock().view!.title).to.equal('title Kim')\n expect(dock().view!.context).to.equal('id 42')\n expect(entity360Sections('k2').map(section => section.name)).to.deep.equal(['first', 'later'])\n })\n\n it('★ a kind with no section says so in one line - never a blank dock', async () => {\n registerEntity360Kind({ kind: 'k3', typenames: ['T3'], title: s => s.value.name })\n\n openEntity360For({ __typename: 'T3', id: '7', name: 'Lee' })\n await settle()\n\n const content = await fixture(html`<div>${dock().view!.content}</div>`)\n expect(content.querySelector('[data-not-yet]')).to.exist\n })\n\n it('★ refuses a second kind of the same name and a typename already claimed', () => {\n registerEntity360Kind({ kind: 'k4', typenames: ['T4'], title: () => '' })\n registerEntity360Kind({ kind: 'k4', typenames: ['T4b'], title: () => '' })\n registerEntity360Kind({ kind: 'k5', typenames: ['T4'], title: () => '' })\n\n const reasons = entity360Rejections().filter(one => one.kind === 'k4' || one.kind === 'k5').map(one => one.reason)\n expect(reasons).to.include('duplicate-kind')\n expect(reasons).to.include('typename-claimed')\n expect(entity360KindOf({ __typename: 'T4' })).to.equal('k4')\n })\n\n it('refuses a section name used twice on one kind', () => {\n registerEntity360Section('k6', { name: 'same', render: () => html`` })\n registerEntity360Section('k6', { name: 'same', render: () => html`` })\n expect(entity360Sections('k6').length).to.equal(1)\n expect(entity360Rejections().some(one => one.kind === 'k6' && one.reason === 'duplicate-name')).to.equal(true)\n })\n})\n\nvoid hideEntity360\n"]}
|