dsh-background-by-model 0.4.4 → 0.4.5
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/README.md +7 -0
- package/README.zh.md +7 -0
- package/lib/client.js +50 -27
- package/lib/client.js.map +1 -1
- package/lib/{host-scan-B-xmbcxD.js → host-scan-C6rdxkf8.js} +14 -10
- package/lib/host-scan.js +1 -1
- package/lib/index.js +1 -1
- package/lib/testing.js +36 -17
- package/package.json +1 -1
- package/test/host-check.test.mjs +133 -11
|
@@ -219,7 +219,8 @@ const HOST_CONTRACTS = [
|
|
|
219
219
|
checks: [{
|
|
220
220
|
kind: "selector",
|
|
221
221
|
selector: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]",
|
|
222
|
-
optionalWhen: "
|
|
222
|
+
optionalWhen: "settingsOpen",
|
|
223
|
+
visible: true
|
|
223
224
|
}],
|
|
224
225
|
sources: [{
|
|
225
226
|
path: "dsh-client-ui-primitives/lib/index.js",
|
|
@@ -275,7 +276,7 @@ const HOST_CONTRACTS = [
|
|
|
275
276
|
checks: [{
|
|
276
277
|
kind: "attr",
|
|
277
278
|
attr: "data-composer-card",
|
|
278
|
-
optionalWhen: "
|
|
279
|
+
optionalWhen: "chatHasMessages"
|
|
279
280
|
}],
|
|
280
281
|
sources: [{
|
|
281
282
|
path: "dsh-client-ui-conversation/lib/client.js",
|
|
@@ -301,7 +302,7 @@ const HOST_CONTRACTS = [
|
|
|
301
302
|
checks: [{
|
|
302
303
|
kind: "attr",
|
|
303
304
|
attr: "data-cordis-panel",
|
|
304
|
-
optionalWhen: "
|
|
305
|
+
optionalWhen: "cordisMounted"
|
|
305
306
|
}],
|
|
306
307
|
sources: [{
|
|
307
308
|
path: "dsh-client-ui-cordis/lib/client.js",
|
|
@@ -327,7 +328,8 @@ const HOST_CONTRACTS = [
|
|
|
327
328
|
checks: [{
|
|
328
329
|
kind: "selector",
|
|
329
330
|
selector: "div[data-sidebar-right-panel]",
|
|
330
|
-
optionalWhen: "
|
|
331
|
+
optionalWhen: "rightPanelMounted",
|
|
332
|
+
visible: true
|
|
331
333
|
}],
|
|
332
334
|
sources: [{
|
|
333
335
|
path: `${SIDEBAR_RIGHT}/lib/client.js`,
|
|
@@ -379,7 +381,8 @@ const HOST_CONTRACTS = [
|
|
|
379
381
|
checks: [{
|
|
380
382
|
kind: "selector",
|
|
381
383
|
selector: "[data-dockkit-content]",
|
|
382
|
-
optionalWhen: "
|
|
384
|
+
optionalWhen: "dockHasTab",
|
|
385
|
+
visible: true
|
|
383
386
|
}],
|
|
384
387
|
sources: [{
|
|
385
388
|
path: ".",
|
|
@@ -406,7 +409,7 @@ const HOST_CONTRACTS = [
|
|
|
406
409
|
checks: [{
|
|
407
410
|
kind: "selector",
|
|
408
411
|
selector: "[data-dockkit-empty]",
|
|
409
|
-
optionalWhen: "
|
|
412
|
+
optionalWhen: "dockHasNoTab"
|
|
410
413
|
}],
|
|
411
414
|
sources: [{
|
|
412
415
|
path: `${SIDEBAR_RIGHT}/lib/client.js`,
|
|
@@ -432,7 +435,7 @@ const HOST_CONTRACTS = [
|
|
|
432
435
|
checks: [{
|
|
433
436
|
kind: "selector",
|
|
434
437
|
selector: "[data-dockkit-float]",
|
|
435
|
-
optionalWhen: "
|
|
438
|
+
optionalWhen: "paneFloated"
|
|
436
439
|
}],
|
|
437
440
|
sources: [{
|
|
438
441
|
path: `${SIDEBAR_RIGHT}/lib/client.js`,
|
|
@@ -458,7 +461,8 @@ const HOST_CONTRACTS = [
|
|
|
458
461
|
checks: [{
|
|
459
462
|
kind: "selector",
|
|
460
463
|
selector: "[data-chat-flow]",
|
|
461
|
-
optionalWhen: "
|
|
464
|
+
optionalWhen: "chatHasMessages",
|
|
465
|
+
visible: true
|
|
462
466
|
}],
|
|
463
467
|
sources: [{
|
|
464
468
|
path: "dsh-client-ui-chat/lib/client.js",
|
|
@@ -484,7 +488,7 @@ const HOST_CONTRACTS = [
|
|
|
484
488
|
checks: [{
|
|
485
489
|
kind: "selector",
|
|
486
490
|
selector: "[data-conversation-scroll]",
|
|
487
|
-
optionalWhen: "
|
|
491
|
+
optionalWhen: "chatHasMessages"
|
|
488
492
|
}],
|
|
489
493
|
sources: [{
|
|
490
494
|
path: "dsh-client-ui-conversation/lib/client.js",
|
|
@@ -510,7 +514,7 @@ const HOST_CONTRACTS = [
|
|
|
510
514
|
checks: [{
|
|
511
515
|
kind: "selector",
|
|
512
516
|
selector: "[data-conversation-composer-overlay]",
|
|
513
|
-
optionalWhen: "
|
|
517
|
+
optionalWhen: "trajectoryMounted"
|
|
514
518
|
}],
|
|
515
519
|
sources: [{
|
|
516
520
|
path: "dsh-client-ui-trajectory/lib/client.js",
|
package/lib/host-scan.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as thisPluginDir, c as DSH_FLOOR, i as resolveHostRoots, l as HOST_CONTRACTS, n as listHostPackages, o as versionOf, r as meetsFloor, t as checkHostOnDisk } from "./host-scan-
|
|
1
|
+
import { a as thisPluginDir, c as DSH_FLOOR, i as resolveHostRoots, l as HOST_CONTRACTS, n as listHostPackages, o as versionOf, r as meetsFloor, t as checkHostOnDisk } from "./host-scan-C6rdxkf8.js";
|
|
2
2
|
export { DSH_FLOOR, HOST_CONTRACTS, checkHostOnDisk, listHostPackages, meetsFloor, resolveHostRoots, thisPluginDir, versionOf };
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as checkHostOnDisk } from "./host-scan-
|
|
1
|
+
import { t as checkHostOnDisk } from "./host-scan-C6rdxkf8.js";
|
|
2
2
|
import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
package/lib/testing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as DSH_FLOOR, d as reportToMarkdown, f as symptomOf, i as resolveHostRoots, l as HOST_CONTRACTS, o as versionOf, p as verdictOf, r as meetsFloor, s as CONTRACT_GROUPS, t as checkHostOnDisk, u as labelOf } from "./host-scan-
|
|
1
|
+
import { c as DSH_FLOOR, d as reportToMarkdown, f as symptomOf, i as resolveHostRoots, l as HOST_CONTRACTS, o as versionOf, p as verdictOf, r as meetsFloor, s as CONTRACT_GROUPS, t as checkHostOnDisk, u as labelOf } from "./host-scan-C6rdxkf8.js";
|
|
2
2
|
const UI_CSS = `
|
|
3
3
|
/* The section is rendered INLINE inside the host settings dialog's content
|
|
4
4
|
* column: the host provides the modal chrome (backdrop, centering, closing).
|
|
@@ -288,26 +288,43 @@ function browserEnv(ctx) {
|
|
|
288
288
|
if (host === null) return "";
|
|
289
289
|
return getComputedStyle(host).getPropertyValue(token).trim();
|
|
290
290
|
},
|
|
291
|
+
visible: (el) => {
|
|
292
|
+
if (el === null || typeof getComputedStyle !== "function") return true;
|
|
293
|
+
try {
|
|
294
|
+
return getComputedStyle(el).visibility !== "hidden";
|
|
295
|
+
} catch {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
},
|
|
291
299
|
styleSheets: () => typeof document === "undefined" ? [] : Array.from(document.styleSheets),
|
|
292
300
|
ctx
|
|
293
301
|
};
|
|
294
302
|
}
|
|
295
303
|
const ANCHORS = {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
304
|
+
chatHasMessages: "[data-chat-turn]",
|
|
305
|
+
trajectoryMounted: "[data-conversation-composer-overlay]",
|
|
306
|
+
rightPanelMounted: "div[data-sidebar-right-panel],[data-dockkit-strip],[data-dockkit-pane]",
|
|
307
|
+
cordisMounted: "[data-cordis-panel]",
|
|
308
|
+
dockHasTab: "[data-dockkit-tab]",
|
|
309
|
+
dockHasNoTab: "[data-dockkit-empty],[data-dockkit-add-tab]",
|
|
310
|
+
paneFloated: "[data-dockkit-float],[data-sidebar-right-mode=\"float\"]",
|
|
311
|
+
settingsOpen: "div[role=\"dialog\"][aria-modal=\"true\"][aria-labelledby]"
|
|
303
312
|
};
|
|
304
|
-
|
|
313
|
+
/**
|
|
314
|
+
* Whether an anchor's surface is on screen.
|
|
315
|
+
*
|
|
316
|
+
* `optionalWhen: X` skips the check while this is FALSE — "the feature is not
|
|
317
|
+
* observable right now", which is what the field name promises. Every anchor is
|
|
318
|
+
* therefore written as a POSITIVE predicate (`dockHasTab`, `chatHasMessages`), and
|
|
319
|
+
* an anchor whose name reads as an absence would silently invert its check.
|
|
320
|
+
*/
|
|
321
|
+
function anchorMounted(env, anchorId) {
|
|
305
322
|
const sel = ANCHORS[anchorId];
|
|
306
323
|
if (sel === void 0) return true;
|
|
307
324
|
try {
|
|
308
325
|
return env.querySelector(sel) !== null;
|
|
309
326
|
} catch {
|
|
310
|
-
return
|
|
327
|
+
return true;
|
|
311
328
|
}
|
|
312
329
|
}
|
|
313
330
|
/** Walk a dotted path off a root object, tolerating a missing first hop. */
|
|
@@ -373,7 +390,9 @@ function ruleMentioned(env, literal, side) {
|
|
|
373
390
|
}
|
|
374
391
|
return false;
|
|
375
392
|
}
|
|
376
|
-
/**
|
|
393
|
+
/**
|
|
394
|
+
* Which sides of stylesheet the probe can actually read (a blind check must not fail).
|
|
395
|
+
*/
|
|
377
396
|
function readability(env) {
|
|
378
397
|
const out = {
|
|
379
398
|
host: false,
|
|
@@ -453,14 +472,14 @@ function evaluate(env, check) {
|
|
|
453
472
|
};
|
|
454
473
|
}
|
|
455
474
|
case "selector": {
|
|
456
|
-
const
|
|
475
|
+
const el = env.querySelector(check.selector);
|
|
457
476
|
const name = `querySelector(${JSON.stringify(check.selector)})`;
|
|
458
|
-
if (
|
|
477
|
+
if (el !== null && (check.visible !== true || env.visible(el))) return {
|
|
459
478
|
status: "pass",
|
|
460
479
|
name,
|
|
461
480
|
detail: "element found"
|
|
462
481
|
};
|
|
463
|
-
if (check.optionalWhen !== void 0 && !
|
|
482
|
+
if (check.optionalWhen !== void 0 && !anchorMounted(env, check.optionalWhen)) return {
|
|
464
483
|
status: "skip",
|
|
465
484
|
name,
|
|
466
485
|
detail: "not on screen right now"
|
|
@@ -468,8 +487,8 @@ function evaluate(env, check) {
|
|
|
468
487
|
return {
|
|
469
488
|
status: "fail",
|
|
470
489
|
name,
|
|
471
|
-
detail: "no element matches",
|
|
472
|
-
reason: `no element matches ${check.selector} while its surface is on screen`
|
|
490
|
+
detail: el === null ? "no element matches" : "the element is hidden",
|
|
491
|
+
reason: el === null ? `no element matches ${check.selector} while its surface is on screen` : `${check.selector} is in the DOM but hidden while its surface is on screen`
|
|
473
492
|
};
|
|
474
493
|
}
|
|
475
494
|
case "attr": {
|
|
@@ -480,7 +499,7 @@ function evaluate(env, check) {
|
|
|
480
499
|
name,
|
|
481
500
|
detail: "attribute found"
|
|
482
501
|
};
|
|
483
|
-
if (check.optionalWhen !== void 0 && !
|
|
502
|
+
if (check.optionalWhen !== void 0 && !anchorMounted(env, check.optionalWhen)) return {
|
|
484
503
|
status: "skip",
|
|
485
504
|
name,
|
|
486
505
|
detail: "not on screen right now"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-background-by-model",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Appearance plugin for DeepSeek Harness: an ordered list of model rules, each carrying its own wallpaper, theme color, layout mode, opacity and blur 鈥?the background follows the model you switch to.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
package/test/host-check.test.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* The host self-check's own tests 鈥?run with
|
|
3
3
|
*
|
|
4
4
|
* pnpm test
|
|
@@ -97,7 +97,11 @@ function makeDom(host) {
|
|
|
97
97
|
part => part.split(/\s+/).every(one),
|
|
98
98
|
)
|
|
99
99
|
return {
|
|
100
|
-
|
|
100
|
+
// The element remembers which selector found it: a stand-in has no box to
|
|
101
|
+
// measure, and several real attributes live on ONE element (the dock's tab
|
|
102
|
+
// host carries both the populated marker and the parked empty seat), so
|
|
103
|
+
// "is this element visible" has to be answered per element, not per fixture.
|
|
104
|
+
querySelector: sel => (matches(sel) ? { style: {}, foundBy: sel } : null),
|
|
101
105
|
// Where the host publishes its palette: `body` in the real client, and the
|
|
102
106
|
// fixture models exactly that. Reading the document element instead is what
|
|
103
107
|
// made seven healthy tokens report as empty.
|
|
@@ -107,6 +111,10 @@ function makeDom(host) {
|
|
|
107
111
|
// table either way 鈥?whether a value is readable is the host's own business,
|
|
108
112
|
// and that is exactly what the table stands in for.
|
|
109
113
|
computedValue: (token, _on) => (host.get(`computed:${token}`) ?? ''),
|
|
114
|
+
// A stand-in has no layout, so visibility is answered from an explicit table
|
|
115
|
+
// (`hidden:<selector>`). Anything unlisted counts as on screen 鈥?the
|
|
116
|
+
// conservative direction: an unanswerable question never fails a check.
|
|
117
|
+
visible: el => el === null || !host.has(`hidden:${el.foundBy}`),
|
|
110
118
|
styleSheets: () => [],
|
|
111
119
|
}
|
|
112
120
|
}
|
|
@@ -119,7 +127,11 @@ const CORE_PRESENT = [
|
|
|
119
127
|
'[data-composer-card]',
|
|
120
128
|
'[data-cordis-panel]',
|
|
121
129
|
'[data-chat-flow]',
|
|
130
|
+
'[data-chat-turn]',
|
|
122
131
|
'[data-conversation-scroll]',
|
|
132
|
+
// The session root, so "no chat view" and "a chat view with nothing in it" can
|
|
133
|
+
// be told apart in the fixture the way they are in the host.
|
|
134
|
+
'[data-conversation-session]',
|
|
123
135
|
'[data-conversation-composer-overlay]',
|
|
124
136
|
'.dab-root',
|
|
125
137
|
'[data-dockkit-strip]',
|
|
@@ -193,35 +205,47 @@ function hostStyles(literals = HOST_RULES, ownLiterals = OWN_RULES) {
|
|
|
193
205
|
/**
|
|
194
206
|
* A fixture host: `selectors` are the ones that match, `rules` the literals the
|
|
195
207
|
* host stylesheet declares, and every token in `PRESENT_TOKENS` resolves unless
|
|
196
|
-
* the caller drops it.
|
|
208
|
+
* the caller drops it. `hidden` lists selectors that are present but parked off
|
|
209
|
+
* screen (`visibility:hidden`) 鈥?the state the dock's empty seat sits in.
|
|
197
210
|
*/
|
|
198
|
-
function hostEnv({ ctx, selectors = CORE_PRESENT, rules = HOST_RULES, tokens = PRESENT_TOKENS }) {
|
|
211
|
+
function hostEnv({ ctx, selectors = CORE_PRESENT, rules = HOST_RULES, tokens = PRESENT_TOKENS, hidden = [] }) {
|
|
199
212
|
// A Map, not a Set: the fixture must answer with the token's VALUE, and only a
|
|
200
213
|
// Map carries one (a Set has no `get`).
|
|
201
214
|
const host = new Map()
|
|
202
215
|
for (const s of selectors) host.set(s, true)
|
|
216
|
+
for (const s of hidden) host.set(`hidden:${s}`, true)
|
|
203
217
|
for (const t of tokens) host.set(`computed:${t}`, '#123456')
|
|
204
218
|
return { ...makeDom(host), styleSheets: () => hostStyles(rules), ctx }
|
|
205
219
|
}
|
|
206
220
|
|
|
207
221
|
/**
|
|
208
|
-
* The two states the dock alternates between. In the real DOM
|
|
209
|
-
* different
|
|
210
|
-
*
|
|
211
|
-
* whether its own attribute can be observed 鈥?so the fixture provides the anchor
|
|
222
|
+
* The two states the dock alternates between. In the real DOM the tab host and
|
|
223
|
+
* the empty seat are different elements, and each contract's anchor is what says
|
|
224
|
+
* whether its own attribute can be observed — so the fixture provides the anchor
|
|
212
225
|
* for every contract it expects to answer, and omits the anchors of the ones it
|
|
213
226
|
* expects to read as n/a.
|
|
227
|
+
*
|
|
228
|
+
* The panel selector is listed twice on purpose: the fixture matches selectors by
|
|
229
|
+
* exact text (it has no tree to walk), and the contracts spell it both as
|
|
230
|
+
* `[data-sidebar-right-panel]` (the anchor) and `div[data-sidebar-right-panel]`
|
|
231
|
+
* (the check).
|
|
214
232
|
*/
|
|
215
233
|
const DOCK_POPULATED = [
|
|
234
|
+
'[data-sidebar-right-panel]',
|
|
216
235
|
'div[data-sidebar-right-panel]',
|
|
217
236
|
'[data-dockkit-tab]',
|
|
218
237
|
'[data-dockkit-content]',
|
|
219
238
|
'[data-dockkit-empty]',
|
|
220
239
|
]
|
|
221
|
-
const DOCK_EMPTY = [
|
|
240
|
+
const DOCK_EMPTY = [
|
|
241
|
+
'[data-sidebar-right-panel]',
|
|
242
|
+
'div[data-sidebar-right-panel]',
|
|
243
|
+
'[data-dockkit-empty]',
|
|
244
|
+
]
|
|
222
245
|
|
|
223
246
|
/** The healthy fixtures: nothing missing, every service published. */
|
|
224
|
-
const healthyEnv = (dock = DOCK_POPULATED) => hostEnv({
|
|
247
|
+
const healthyEnv = (dock = DOCK_POPULATED, extra = {}) => hostEnv({
|
|
248
|
+
...extra,
|
|
225
249
|
ctx: healthyCtx(),
|
|
226
250
|
selectors: [...CORE_PRESENT, ...dock],
|
|
227
251
|
})
|
|
@@ -343,12 +367,110 @@ test('a closed dock holding only its empty seat is n/a, not a failure', () => {
|
|
|
343
367
|
const results = probeContracts(env, 'en')
|
|
344
368
|
const byId = id => results.find(r => r.id === id)
|
|
345
369
|
assert.equal(byId('dockkit.content').status, 'skip')
|
|
346
|
-
// The empty seat IS on screen here, so its own contract answers
|
|
370
|
+
// The empty seat IS the thing on screen here, so its own contract answers — it
|
|
371
|
+
// is present (the host renders it visibly in this state; it is only parked and
|
|
372
|
+
// hidden once a tab opens).
|
|
347
373
|
assert.equal(byId('dockkit.empty').status, 'pass')
|
|
348
374
|
assert.equal(byId('dockkit.float').status, 'skip')
|
|
349
375
|
assert.deepEqual(results.filter(r => r.status === 'fail').map(r => r.id), [])
|
|
350
376
|
})
|
|
351
377
|
|
|
378
|
+
test('a populated dock reports its empty seat as n/a, not as missing', () => {
|
|
379
|
+
// The state the panel showed the user a red line for. With a tab open the host
|
|
380
|
+
// renders no seat on that screen at all — the seat is not the thing being shown —
|
|
381
|
+
// so the question has no answer there, which is n/a and must never be a failure.
|
|
382
|
+
const env = hostEnv({
|
|
383
|
+
ctx: healthyCtx(),
|
|
384
|
+
selectors: [...CORE_PRESENT, 'div[data-sidebar-right-panel]', '[data-dockkit-tab]', '[data-dockkit-content]'],
|
|
385
|
+
})
|
|
386
|
+
const results = probeContracts(env, 'en')
|
|
387
|
+
const byId = id => results.find(r => r.id === id)
|
|
388
|
+
assert.equal(byId('dockkit.empty').status, 'skip')
|
|
389
|
+
assert.equal(byId('dockkit.content').status, 'pass')
|
|
390
|
+
assert.deepEqual(results.filter(r => r.status === 'fail').map(r => r.id), [])
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test('a parked empty seat is still a pass, not a failure', () => {
|
|
394
|
+
// Presence is the whole question. The host parks the seat with
|
|
395
|
+
// `visibility:hidden` as tabs come and go, so asking whether it is VISIBLE is
|
|
396
|
+
// asking about a state the host uses both ways — which is how this contract ended
|
|
397
|
+
// up reporting a red line on a healthy dock.
|
|
398
|
+
const env = hostEnv({
|
|
399
|
+
ctx: healthyCtx(),
|
|
400
|
+
selectors: [...CORE_PRESENT, 'div[data-sidebar-right-panel]', '[data-dockkit-add-tab]', '[data-dockkit-empty]'],
|
|
401
|
+
hidden: ['[data-dockkit-empty]'],
|
|
402
|
+
})
|
|
403
|
+
const results = probeContracts(env, 'en')
|
|
404
|
+
assert.equal(results.find(r => r.id === 'dockkit.empty').status, 'pass')
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test('a seat that is gone while the dock is empty fails', () => {
|
|
408
|
+
// The real rename, and the one way this contract can fail: the dock is on screen
|
|
409
|
+
// with the host's "add a tab" button — so it holds no tab — and the seat is not
|
|
410
|
+
// there. (The old form tested the opposite state, a populated dock, instead.)
|
|
411
|
+
const env = hostEnv({
|
|
412
|
+
ctx: healthyCtx(),
|
|
413
|
+
selectors: [...CORE_PRESENT, 'div[data-sidebar-right-panel]', '[data-dockkit-add-tab]'],
|
|
414
|
+
})
|
|
415
|
+
const results = probeContracts(env, 'en')
|
|
416
|
+
const row = results.find(r => r.id === 'dockkit.empty')
|
|
417
|
+
assert.equal(row.status, 'fail')
|
|
418
|
+
assert.match(row.reason, /no element matches/)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
test('a surface that is mounted and empty is n/a, not a renamed marker', () => {
|
|
422
|
+
// The hero screen: a session is open, the composer is there, and the host has
|
|
423
|
+
// deliberately rendered no message column because there are no messages. The
|
|
424
|
+
// previous anchor ("the conversation scrollport exists") is true here, so
|
|
425
|
+
// `[data-chat-flow]` was reported as renamed on a healthy, brand-new session.
|
|
426
|
+
const env = hostEnv({
|
|
427
|
+
ctx: healthyCtx(),
|
|
428
|
+
selectors: ['[data-conversation-session]', '[data-conversation-scroll]', '[data-composer-card]',
|
|
429
|
+
'[data-content-phase]', '[data-shell-overlay]', '.dab-root', ...DOCK_EMPTY],
|
|
430
|
+
})
|
|
431
|
+
const results = probeContracts(env, 'en')
|
|
432
|
+
const byId = id => results.find(r => r.id === id)
|
|
433
|
+
assert.equal(byId('chat.flow').status, 'skip')
|
|
434
|
+
// The two markers that ARE expected in that phase still have to answer, or this
|
|
435
|
+
// guard would hide a real rename behind "the chat is not mounted".
|
|
436
|
+
assert.equal(byId('composer.card').status, 'pass')
|
|
437
|
+
assert.equal(byId('conversation.scroll').status, 'pass')
|
|
438
|
+
assert.deepEqual(results.filter(r => r.status === 'fail').map(r => r.id), [])
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
test('the trajectory tab is n/a for the chat column, not a failure', () => {
|
|
442
|
+
// Measured on the running host: the trajectory view unmounts the chat column
|
|
443
|
+
// (`data-trajectory-scroll` in, `data-chat-flow` out) while the session root
|
|
444
|
+
// changes phase rather than disappearing. Switching to it used to report
|
|
445
|
+
// "no element matches [data-chat-flow] while its surface is on screen".
|
|
446
|
+
const env = hostEnv({
|
|
447
|
+
ctx: healthyCtx(),
|
|
448
|
+
selectors: ['[data-conversation-session]', '[data-content-phase]', '[data-conversation-scroll]',
|
|
449
|
+
'[data-composer-card]', '[data-conversation-composer-overlay]', '[data-shell-overlay]', '.dab-root',
|
|
450
|
+
...DOCK_POPULATED],
|
|
451
|
+
})
|
|
452
|
+
const results = probeContracts(env, 'en')
|
|
453
|
+
const byId = id => results.find(r => r.id === id)
|
|
454
|
+
assert.equal(byId('chat.flow').status, 'skip')
|
|
455
|
+
assert.equal(byId('trajectory.root').status, 'pass')
|
|
456
|
+
assert.deepEqual(results.filter(r => r.status === 'fail').map(r => r.id), [])
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
test('a message column that is present but hidden is a failure', () => {
|
|
460
|
+
// The other half of the fix: `visible` must not soften the check into "it is
|
|
461
|
+
// somewhere in the DOM". A column the user cannot see is not a column the card
|
|
462
|
+
// can be drawn on, and the report has to say which of the two happened.
|
|
463
|
+
const env = hostEnv({
|
|
464
|
+
ctx: healthyCtx(),
|
|
465
|
+
selectors: [...CORE_PRESENT, ...DOCK_POPULATED],
|
|
466
|
+
hidden: ['[data-chat-flow]'],
|
|
467
|
+
})
|
|
468
|
+
const results = probeContracts(env, 'en')
|
|
469
|
+
const row = results.find(r => r.id === 'chat.flow')
|
|
470
|
+
assert.equal(row.status, 'fail')
|
|
471
|
+
assert.match(row.reason, /hidden/)
|
|
472
|
+
})
|
|
473
|
+
|
|
352
474
|
test('a renamed dock tab attribute still fails once a tab is open', () => {
|
|
353
475
|
// The guard must not swallow a real rename: with a tab open the host has to
|
|
354
476
|
// carry `data-dockkit-content` on the tab host, and if the name moved, say so.
|