android: the faces the walk had never seen — six failure states, read back in their own words
Round thirty-one gave the phone honest error states and shipped six of them unobserved, because neither `loadHome` nor `loadLyrics` was reachable from the `__ss` handle and both answer a second ask with nothing. The handle carries the two stores and their two loaders now, and a block of mock steps arms each failure with the mock's own weather, drives the face, and asserts the sentence a person reads as well as the absence of the Rust string behind it: Home's catalog line, the Gaps line and its second tap, a catalog record's stub, the artist page's two different answers, a playlist that would not read, the lyrics lookup that failed, a refused transfer, and the library toast's Try again, pressed for the first time. It sits before the first-run block, which reloads and empties the mock's follows; every failure is disarmed by the step that proves the face heals, and a last step asserts nothing was left armed. The phone walk gets a safe step of its own — a terminal transfer row written into the store and taken away, nothing fetched and nothing reaching the shade. ANDROID.md now names what the walk still cannot see: three screens that draw a raw backend string, six error faces with no expectation, and the history mirror printed on every step and asserted on none.
This commit is contained in:
1 parent
f2cde4ec7c
commit
537bfdd5af
3 files changed
+235
-5
No files matched your search
@@ -347,6 +347,26 @@ const PROBE = `(function () {
|
||||
folderGone: (document.querySelector('.folder-gone') || {}).textContent || '',
|
||||
lyrNote: (document.querySelector('.lyr-note') || {}).textContent || '',
|
||||
lyrTools: !!document.querySelector('.lyr-tools'),
|
||||
/* ── the faces round thirty-one drew and no step had ever read ──
|
||||
Home's own line and the slots behind it (how many follows it
|
||||
walked, how many came back empty-handed); the catalog's note on
|
||||
an artist page; whatever stub is up, whitespace collapsed; the
|
||||
Gaps line, the BUTTON only — the '.gapline.done' variant is a
|
||||
div and says something else entirely; the failed transfer's
|
||||
sentence; and whether the lyrics lookup itself failed, as
|
||||
opposed to having found nothing. Every backslash here is doubled
|
||||
and there is not one backtick, comment included — this block
|
||||
lives inside a template literal, and a backtick would END it
|
||||
(the file still PARSES, so node --check says nothing: what it
|
||||
becomes is a tagged template, and smoke.mjs throws on import). */
|
||||
catDown: (document.querySelector('.sec-note.cat-down') || {}).textContent || '',
|
||||
homeSlots: (function () { try { return __ss.home.order.length; } catch (e) { return -1; } })(),
|
||||
homeErr: (function () { try { return __ss.home.order.filter(function (id) { var s = __ss.home.slots[id]; return !!(s && s.error); }).length; } catch (e) { return -1; } })(),
|
||||
catNote: (function () { var n = [].slice.call(document.querySelectorAll('.screen .sec-note:not(.cat-down)')).find(function (x) { return /catalog/i.test(x.textContent || ''); }); return n ? n.textContent.replace(/\\s+/g, ' ').trim() : ''; })(),
|
||||
stubText: ((document.querySelector('.screen .stub') || {}).textContent || '').replace(/\\s+/g, ' ').trim(),
|
||||
gapLine: ((document.querySelector('button.gapline') || {}).textContent || '').replace(/\\s+/g, ' ').trim(),
|
||||
xferFail: (document.querySelector('.row.xfer.failed .m') || {}).textContent || '',
|
||||
lyrFailed: (function () { try { return __ss.lyr.failed ? 'yes' : 'no'; } catch (e) { return ''; } })(),
|
||||
trks: document.querySelectorAll('.screen .trk').length,
|
||||
/* the phone's trouble-words step leaves its eleven sentences here */
|
||||
trouble: (function () { try { return String(window.__trouble || ''); } catch (e) { return ''; } })(),
|
||||
@@ -971,6 +991,105 @@ const MOCK_STEPS = [
|
||||
{ do: `(function () { __ss.slsk.roomsWaitMs = 12000; __ssMock.clear(); __ss.refreshRooms(); __ss.player.error = ''; __ss.lib.error = ''; __ss.player.retry = null; return true; })()`, wait: 900 },
|
||||
{ do: `__settle()`, wait: 900 },
|
||||
{ do: `document.querySelectorAll('.nav button')[0].click()`, name: "weather-clear", wait: 1000 },
|
||||
/* ── the faces no walk had seen ─────────────────────────────────────
|
||||
Round thirty-one gave six screens an honest failure and its own
|
||||
verifier was straight about what it had shipped unobserved: Home's
|
||||
`catalogDown`, the lyrics `failed` note, Gaps' `gapErr`, Playlist's
|
||||
`loadErr`, the catalog record's and the artist page's stubs, and
|
||||
`failed · <sentence>` on a transfer row. It could drive none of them
|
||||
because neither `loadHome` nor `loadLyrics` was reachable from the
|
||||
`__ss` handle — both are gated on `started` / `key` and answer a
|
||||
second ask with nothing at all, so no amount of tapping re-runs
|
||||
them. The handle carries the two stores and their two loaders now,
|
||||
and this block is what they were added for.
|
||||
|
||||
It sits HERE, before the first run, and not at the end of the list:
|
||||
the block below reloads the page twice, and the mock's follows live
|
||||
in a module-level `let` that a reload empties — after `fresh-off`
|
||||
nobody is followed, and Home's `catalogDown` (which needs a follow)
|
||||
and the Gaps shelf (which IS the follows) are both structurally
|
||||
unreachable. Here the block inherits what the weather left behind:
|
||||
signed in, on Home, Daft Punk followed, a track on the mini bar.
|
||||
|
||||
Every assertion is on settled state — an armed failure stays armed,
|
||||
an error stub stays up, the toast stays until it is dismissed — and
|
||||
the two forced loads return their promise, which `cdp.evaluate`
|
||||
awaits, so the fetch is over before the step's wait even begins.
|
||||
Every failure is disarmed by the step that proves the face heals,
|
||||
and `faces-clear` is the proof that nothing was left armed. */
|
||||
{ do: `(function () { __ssMock.clear(); __ss.player.error = ''; __ss.lib.error = ''; __ss.player.retry = null; return __settle(); })()`, wait: 800 },
|
||||
/* Home with every follow's catalog page refused. `home.svelte.js` has
|
||||
always set `slot.error` there; until round thirty-one nothing read
|
||||
it, and until this step nothing had ever seen the line that does. */
|
||||
{ do: `(function () { __ssMock.fail('catalog_artist', 'io: failed to lookup address information: no address associated with hostname'); return __ss.loadHome(true); })()`, name: "faces-home-down", wait: 1200 },
|
||||
/* the same armed failure, on the other screen that asks the same
|
||||
command: Gaps. Home stays poisoned through these three steps on
|
||||
purpose — it is asserted above, and the second tap below is what
|
||||
clears the weather for both. */
|
||||
{ do: `document.querySelectorAll('.nav button')[2].click()`, wait: 800 },
|
||||
{ do: `__click('.segtabs button', 'Gaps')`, wait: 700 },
|
||||
{ do: `__click('.gapline', null)`, name: "faces-gap-down", wait: 1400 },
|
||||
/* a failed lookup leaves `gapRows[key]` undefined, so the second tap
|
||||
genuinely asks again rather than merely opening a rail */
|
||||
{ do: `__ssMock.fail('catalog_artist', '')`, wait: 300 },
|
||||
{ do: `__click('.gapline', null)`, name: "faces-gap-again", wait: 1600 },
|
||||
/* a catalog record. The walk has already made 302127 `ready` and
|
||||
`lookupAlbum` returns early on anything but an error, so the entry is
|
||||
forgotten first — the smallest honest way to say "ask again". */
|
||||
{ do: `(function () { delete __ss.catalog.albums[302127]; return __ssMock.fail('catalog_album', 'io: connection reset by peer'); })()`, wait: 300 },
|
||||
{ do: `__ss.push('catalogAlbum', { id: 302127, artist: 'Daft Punk', title: 'Discovery' })`, name: "faces-album-down", wait: 1500 },
|
||||
{ do: `__ssMock.fail('catalog_album', '')`, wait: 300 },
|
||||
{ do: `__click('.imp-foot .btn', 'Try again')`, name: "faces-album-again", wait: 1600 },
|
||||
{ do: `__ss.back()`, wait: 700 },
|
||||
/* the artist page has TWO of these and they say different things: a
|
||||
lookup that FAILED is "didn't answer", one that came back empty is
|
||||
"had nothing to add". `bestArtistFor` is what has to fail for the
|
||||
first — failing `catalog_artist` alone never reaches the page at all
|
||||
and leaves the status at none, which is the other copy. */
|
||||
{ do: `__ssMock.fail('catalog_search', 'io: failed to lookup address information: no address associated with hostname')`, wait: 250 },
|
||||
{ do: `__ss.push('artist', { name: 'Hollow Signal' })`, name: "faces-artist-down", wait: 1600 },
|
||||
{ do: `(function () { __ssMock.fail('catalog_search', ''); __ss.back(); return true; })()`, wait: 700 },
|
||||
{ do: `__ss.push('artist', { name: 'Hollow Signal' })`, name: "faces-artist-none", wait: 1600 },
|
||||
{ do: `__ss.back()`, wait: 700 },
|
||||
/* a playlist that will not read. Id 1 survives the whole walk — it is
|
||||
the one renamed to "Late night drive II"; the one deleted was New
|
||||
Playlist. Try again clears `loadErr` and bumps `again`, which is
|
||||
what re-arms the effect. */
|
||||
{ do: `__ssMock.fail('playlist_items', 'unable to open database file')`, wait: 250 },
|
||||
{ do: `__ss.push('playlist', { id: 1 })`, name: "faces-playlist-down", wait: 1400 },
|
||||
{ do: `__ssMock.fail('playlist_items', '')`, wait: 300 },
|
||||
{ do: `__click('.imp-foot .btn', 'Try again')`, name: "faces-playlist-again", wait: 1400 },
|
||||
{ do: `__ss.back()`, wait: 700 },
|
||||
/* the lyrics face. Sheet.svelte asks with the UN-forced `loadLyrics()`,
|
||||
which returns at once while `lyr.key` is already this track's — so
|
||||
the face alone can never ask again, which is exactly why round
|
||||
thirty-one could not drive this one. The loader, forced. */
|
||||
{ do: `(function () { __ss.openSheet(); return true; })()`, wait: 900 },
|
||||
{ do: `document.querySelectorAll('.fbtn')[1].click()`, wait: 900 },
|
||||
{ do: `(function () { __ssMock.fail('lyrics_get', 'io: failed to lookup address information: no address associated with hostname'); return __ss.loadLyrics(true); })()`, name: "faces-lyrics-down", wait: 1200 },
|
||||
{ do: `(function () { __ssMock.fail('lyrics_get', ''); return true; })()`, wait: 250 },
|
||||
{ do: `__ss.loadLyrics(true)`, name: "faces-lyrics-again", wait: 1200 },
|
||||
{ do: `(function () { document.querySelectorAll('.fbtn')[0].click(); __ss.closeSheet(); return true; })()`, wait: 700 },
|
||||
/* a transfer that was refused, written straight into the store the way
|
||||
the shade steps above do — nothing is fetched, and `failed` is in
|
||||
DONE, so `xfernote.shape()` sees nothing in flight and posts no
|
||||
notice. The id is far above the backend's own counter, and `list()`
|
||||
sorts id-descending, so this row is the first one on the screen. */
|
||||
{ do: `(function () { __ss.transfers.items[990011] = { id: 990011, username: 'walk_peer', path: 'Walk\\\\walk-c.flac', name: 'walk-c.flac', state: 'failed', queue_place: null, local_path: null, error: 'transfer rejected: File not shared.', received: 0, total: 0 }; __ss.push('transfers', {}); return true; })()`, name: "faces-xfer-failed", wait: 1300 },
|
||||
{ do: `(function () { delete __ss.transfers.items[990011]; __ss.back(); return true; })()`, wait: 800 },
|
||||
/* the one toast action round thirty-one armed that no step had ever
|
||||
pressed. `lib.error` is written by `loadLibrary` alone —
|
||||
`watchLibrary`'s own reload swallows its failure — so You → Rescan
|
||||
library is the only door in the app that reaches it twice. */
|
||||
{ do: `document.querySelectorAll('.nav button')[4].click()`, wait: 800 },
|
||||
{ do: `__ssMock.fail('library_albums', 'unable to open database file')`, wait: 250 },
|
||||
{ do: `__click('.screen .row', 'Rescan library')`, name: "faces-library-toast", wait: 1500 },
|
||||
{ do: `__ssMock.fail('library_albums', '')`, wait: 300 },
|
||||
{ do: `__click('.toast:not(.notice) .na', 'Try again')`, name: "faces-library-again", wait: 1400 },
|
||||
/* and all of it put away, so the first run below starts exactly where
|
||||
it would have started without this block at all */
|
||||
{ do: `(function () { __ssMock.clear(); __ss.player.error = ''; __ss.lib.error = ''; __ss.player.retry = null; __settle(); return __ss.loadHome(true); })()`, wait: 900 },
|
||||
{ name: "faces-clear", wait: 1200 },
|
||||
/* ── the first run, on a device that has just been installed ──────
|
||||
The mock is the whole backend here, so it can answer as one: no
|
||||
records, no playlists, no account, no all-files permission, and the
|
||||
@@ -1108,6 +1227,46 @@ const MOCK_EXPECT = {
|
||||
"weather-rescan": (i) => /1A2B-3C4D/.test(i.folderGone) && /isn't there any more/.test(i.toast) && /1A2B-3C4D/.test(i.toast)
|
||||
&& !/library-folder-missing/.test(i.toast + i.folderGone),
|
||||
"weather-clear": (i) => !i.toast && !i.why && !i.folderGone && !i.roomsNote && i.slskOn === "online" && /^(Good |Still)/.test(i.title),
|
||||
/* ── the faces no walk had seen ─────────────────────────────────────
|
||||
Each of these fails if round thirty-one's fix is undone: stop reading
|
||||
`slot.error` and Home's line empties; drop the `gapErr` branch and
|
||||
the Gaps line falls back to "See what's missing" and can be tapped
|
||||
for ever; put `lyr.failed` back and the face says "No lyrics found"
|
||||
about a lookup that never answered; take `say()` out of `stateLine`
|
||||
and the raw string is on the row again. Every one asserts the
|
||||
sentence a person reads AND that the Rust string is not in it — the
|
||||
second half is the round-thirty-one rule, and the half that survives
|
||||
any rewording of the first. */
|
||||
"faces-home-down": (i) => i.homeSlots > 0 && i.homeErr === i.homeSlots
|
||||
&& /^Couldn't reach the catalog, so there's nothing new/.test(i.catDown)
|
||||
&& !/New from your artists/.test(i.secs),
|
||||
"faces-gap-down": (i) => /Couldn't reach the catalogue — tap to try again/.test(i.gapLine) && !i.gapCards,
|
||||
"faces-gap-again": (i) => /more released/.test(i.gapLine) && !/Couldn't reach/.test(i.gapLine) && i.gapCards >= 3,
|
||||
"faces-album-down": (i) => /^The catalog did not answer/.test(i.why) && /The network didn't answer/.test(i.why)
|
||||
&& !/io:|connection reset/.test(i.why) && i.again && !i.trks,
|
||||
"faces-album-again": (i) => !i.why && !i.again && i.trks > 0,
|
||||
/* the pair that proves the distinction is real and not decoration */
|
||||
"faces-artist-down": (i) => /only what this phone holds is here/.test(i.catNote) && /didn't answer/.test(i.stubText)
|
||||
&& !/io:|address information/.test(i.catNote + i.stubText),
|
||||
"faces-artist-none": (i) => !i.catNote && /had nothing to add/.test(i.stubText) && !/didn't answer/.test(i.stubText),
|
||||
"faces-playlist-down": (i) => /^Couldn't read this playlist/.test(i.why) && /library folder isn't readable/.test(i.why)
|
||||
&& i.again && !i.plrows && !/database file/.test(i.why),
|
||||
"faces-playlist-again": (i) => !i.why && !i.again && i.plrows > 0,
|
||||
"faces-lyrics-down": (i) => i.lyrFailed === "yes" && !i.lyrLines && /^Couldn't look up the words/.test(i.lyrNote)
|
||||
&& i.lyrTools && !/io:|address information/.test(i.lyrNote),
|
||||
"faces-lyrics-again": (i) => i.lyrFailed === "no" && i.lyrLines >= 3 && !i.lyrNote,
|
||||
"faces-xfer-failed": (i) => i.title.startsWith("Transfers")
|
||||
&& /failed · They refused the transfer \(File not shared\)/.test(i.xferFail)
|
||||
&& !/transfer rejected/.test(i.xferFail),
|
||||
"faces-library-toast": (i) => /library folder isn't readable right now/.test(i.toast) && i.retry === "Try again"
|
||||
&& !/database file/.test(i.toast),
|
||||
"faces-library-again": (i) => i.title.startsWith("You") && !i.toast && !i.retry,
|
||||
/* the disarm, proved: no toast, no armed action, no stub, Home's slots
|
||||
healthy and its rails back — so the first run below is not quietly
|
||||
running on something this block left standing */
|
||||
"faces-clear": (i) => !i.toast && !i.why && !i.catDown && !i.retry
|
||||
&& i.homeSlots > 0 && i.homeErr === 0
|
||||
&& /New from your artists/.test(i.secs) && /^(Good |Still)/.test(i.title),
|
||||
/* sideways: the chrome budget, then every surface that was clipped. The
|
||||
before-value of each is in the rig's now.json — 46% of the height as
|
||||
chrome, a 879px cover with the transport 687px below the fold, a
|
||||
@@ -1604,6 +1763,17 @@ const PHONE_STEPS = [
|
||||
{ do: `document.querySelector('.scrim').click()`, wait: 700 },
|
||||
{ do: `history.back()`, wait: 800 },
|
||||
{ do: `history.back()`, wait: 900 },
|
||||
/* the one face of the six that a real build can show without arming
|
||||
anything: `__ssMock` does not exist here. One already-terminal row
|
||||
goes into the transfer store — the same thing the shade steps above
|
||||
do — and comes straight back out. Nothing is downloaded, no room is
|
||||
joined, no message is sent, nobody is signed in, and neither the
|
||||
config nor the library is written. A `failed` row is not in flight,
|
||||
so no notice is posted either. The id is far above the backend's own
|
||||
counter and `list()` sorts id-descending, so it is the first row on
|
||||
the screen even if Kyle's own session is carrying real failures. */
|
||||
{ do: `(function () { __ss.transfers.items[990011] = { id: 990011, username: 'walk_peer', path: 'Walk\\\\walk-c.flac', name: 'walk-c.flac', state: 'failed', queue_place: null, local_path: null, error: 'transfer rejected: File not shared.', received: 0, total: 0 }; __ss.push('transfers', {}); return true; })()`, name: "trouble-xfer", wait: 1400 },
|
||||
{ do: `(function () { delete __ss.transfers.items[990011]; __ss.back(); return true; })()`, name: "trouble-xfer-gone", wait: 1000 },
|
||||
{ do: `document.querySelectorAll('.nav button')[0].click()`, name: "end", wait: 800 },
|
||||
];
|
||||
const PHONE_EXPECT = {
|
||||
@@ -1737,6 +1907,21 @@ const PHONE_EXPECT = {
|
||||
"dg-page": (i) => i.dgTabs >= 1 && i.dgRows >= 1 && !i.toast && !i.a11yAnon && !i.a11yBad && !i.a11yGhost && !i.a11yState,
|
||||
"dg-sheet": (i) => i.half.includes("up") && /By year/.test(i.halfText) && !i.toast,
|
||||
"dg-az": (i) => i.dgRows >= 1 && /A–Z/.test(i.dgOrder) && !i.toast,
|
||||
/* the row's own words on the device, and then its absence. The "gone"
|
||||
clause is written against THIS row's sentence rather than against
|
||||
`xferFail` being empty: a real failed transfer of Kyle's sitting in
|
||||
the list is not a reason to fail the step, and the real core
|
||||
answering faster than a mock would cannot change either reading —
|
||||
both are settled state, written and removed by the step itself. The
|
||||
screen underneath is deliberately not named: sibling rounds append
|
||||
their own phone steps before `end` too, so whichever screen the back
|
||||
lands on is theirs, not this round's. What IS asserted is that the
|
||||
back popped at all — a sheet left up would close instead and the bar
|
||||
would still say Transfers. */
|
||||
"trouble-xfer": (i) => i.title.startsWith("Transfers")
|
||||
&& /failed · They refused the transfer \(File not shared\)/.test(i.xferFail)
|
||||
&& !/transfer rejected/.test(i.xferFail),
|
||||
"trouble-xfer-gone": (i) => !/^Transfers/.test(i.title) && !/File not shared/.test(i.xferFail),
|
||||
end: (i) => /^(Good |Still)/.test(i.title),
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user