mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
137 lines
7.3 KiB
JavaScript
137 lines
7.3 KiB
JavaScript
import assert from "node:assert/strict"
|
|
import { SettingsPage } from "../web/js/settings.js"
|
|
import { OnroadLayoutPage } from "../web/js/onroad-layout.js"
|
|
|
|
const listeners = new Map()
|
|
globalThis.window = { scrollTo() {}, addEventListener(name, callback) { listeners.set(name, callback) } }
|
|
let current = new URL("https://galaxy.test/app#/cameras/pip")
|
|
globalThis.location = { get hash() { return current.hash }, pathname: "/app", search: "" }
|
|
const entries = [{ url: current.href, state: null }]
|
|
let cursor = 0
|
|
globalThis.history = {
|
|
get state() { return entries[cursor].state },
|
|
replaceState(state, _title, url) { current = new URL(url, current); entries[cursor] = { state, url: current.href } },
|
|
pushState(state, _title, url) { entries.splice(cursor + 1); current = new URL(url, current); entries.push({ state, url: current.href }); cursor++ },
|
|
go(delta) { if (cursor + delta >= 0 && cursor + delta < entries.length) { cursor += delta; current = new URL(entries[cursor].url); listeners.get("popstate")() } },
|
|
back() { if (cursor > 0) { cursor--; current = new URL(entries[cursor].url); listeners.get("popstate")() } },
|
|
forward() { if (cursor < entries.length - 1) { cursor++; current = new URL(entries[cursor].url); listeners.get("popstate")() } },
|
|
}
|
|
const { route, navigate, navigateBack, startRouter } = await import("../web/js/router.js")
|
|
startRouter()
|
|
navigateBack()
|
|
assert.equal(route.path, "/") // A direct link cannot navigate outside Galaxy.
|
|
navigate("/settings"); navigate("/appearance"); navigate("/theme_maker")
|
|
navigateBack(); assert.equal(route.path, "/appearance")
|
|
navigateBack(); assert.equal(route.path, "/settings")
|
|
history.forward(); assert.equal(route.path, "/appearance")
|
|
navigate("/cameras"); navigate("/cameras/pip")
|
|
navigateBack(); assert.equal(route.path, "/cameras")
|
|
const length = entries.length
|
|
navigate("/cameras"); assert.equal(entries.length, length)
|
|
|
|
let returned = 0, cancelled = 0
|
|
const settings = { busy: false, state: { data: { page: "standard/following" } }, initialPage: "hub", atSectionRoot: false,
|
|
back() { returned++ }, feed: { cancel() { cancelled++ } } }
|
|
assert.equal(SettingsPage.methods.navigateBack.call(settings), true)
|
|
assert.equal(returned, 1)
|
|
settings.atSectionRoot = true
|
|
assert.equal(SettingsPage.methods.navigateBack.call(settings), false)
|
|
settings.state.pending = {}; settings.busy = true
|
|
assert.equal(SettingsPage.methods.navigateBack.call(settings), true)
|
|
assert.equal(cancelled, 1)
|
|
|
|
let closed = 0
|
|
const editor = { busy: false, dirty: true, state: { drag: null, discard: null },
|
|
leave: OnroadLayoutPage.methods.leave, $emit(event) { if (event === "close") closed++ } }
|
|
settings.state = { layoutOpen: true }; settings.busy = false
|
|
settings.$refs = { layoutEditor: { requestLeave(action) { OnroadLayoutPage.methods.requestLeave.call(editor, action) } } }
|
|
assert.equal(SettingsPage.methods.navigateBack.call(settings), true)
|
|
assert.equal(editor.state.discard, "back"); assert.equal(closed, 0)
|
|
editor.state.discard = null // Cancel discard keeps the editor.
|
|
assert.equal(closed, 0)
|
|
editor.leave("back"); assert.equal(closed, 1)
|
|
console.log("Galaxy Back history and editor guard passed")
|
|
|
|
// Parent is the page actually visited, not a guessed personality parent.
|
|
const nested = { initialPage: 'hub', state: { section: 'longitudinal', query: '', parents: [], data: {page: 'hub'} },
|
|
activeSection: {id: 'longitudinal'}, feed: {load(page) { nested.state.data = {page} }} }
|
|
SettingsPage.methods.open.call(nested, 'profiles')
|
|
SettingsPage.methods.back.call(nested)
|
|
assert.equal(nested.state.data.page, 'hub')
|
|
SettingsPage.methods.open.call(nested, 'standard')
|
|
SettingsPage.methods.open.call(nested, 'standard/following')
|
|
SettingsPage.methods.back.call(nested)
|
|
assert.equal(nested.state.data.page, 'standard')
|
|
SettingsPage.methods.back.call(nested)
|
|
assert.equal(nested.state.data.page, 'hub', 'direct personality returns to Longitudinal hub')
|
|
SettingsPage.methods.open.call(nested, 'profiles')
|
|
SettingsPage.methods.open.call(nested, 'standard')
|
|
SettingsPage.methods.back.call(nested)
|
|
assert.equal(nested.state.data.page, 'profiles', 'personality opened through planner returns there')
|
|
SettingsPage.methods.selectSection.call({...nested,busy:false,feed:{active:true,load(){}}}, {id:'wheel',pages:['wheel']})
|
|
assert.deepEqual(nested.state.parents, [], 'changing sections retires nested history')
|
|
|
|
nested.initialPage = 'profiles'; nested.state.data = {page:'profiles'}
|
|
SettingsPage.methods.open.call(nested,'standard')
|
|
SettingsPage.methods.open.call(nested,'standard/following')
|
|
SettingsPage.methods.back.call(nested)
|
|
assert.equal(nested.state.data.page,'standard','dedicated tools also retain each nested parent')
|
|
SettingsPage.methods.back.call(nested)
|
|
assert.equal(nested.state.data.page,'profiles')
|
|
|
|
// Routed tools must return to the Settings entry that opened them. Reload
|
|
// saved values from the device; keep only the navigation state in memory.
|
|
navigate('/settings')
|
|
const settingsView = route.view
|
|
const props = {initialPage:'hub',initialSection:null,unauthorized() {}}
|
|
const original = {...SettingsPage.setup(props), ...props}
|
|
Object.assign(original.state, {data:{page:'pip',rows:[{value:'old device value'}]},
|
|
section:'visual',query:'camera',searchOpen:true,parents:['hub']})
|
|
settingsView.scroll = {x:0,y:720}
|
|
SettingsPage.beforeUnmount.call(original)
|
|
assert(!JSON.stringify(settingsView).includes('old device value'))
|
|
navigate('/cameras/pip')
|
|
assert.notEqual(route.view, settingsView)
|
|
navigateBack()
|
|
assert.equal(route.view, settingsView)
|
|
const restored = {...SettingsPage.setup(props), ...props, mode:'local'}
|
|
assert.equal(restored.restoredPage, 'pip')
|
|
assert.equal(restored.state.section, 'visual')
|
|
assert.equal(restored.state.query, 'camera')
|
|
assert.equal(restored.state.searchOpen, true)
|
|
assert.deepEqual(restored.state.parents, ['hub'])
|
|
assert.equal(restored.state.data, null, 'saved settings are always read again')
|
|
const positions = []
|
|
window.scrollTo = (x,y) => positions.push([x,y])
|
|
restored.feed = {active:true,async start(page) {assert.equal(page,'pip')}}
|
|
// Route changes capture the live scroll before unmount, independently of the
|
|
// Settings request lifecycle. Use an explicit restored position here.
|
|
settingsView.scroll = {x:0,y:720}
|
|
await SettingsPage.mounted.call(restored)
|
|
assert.deepEqual(positions.at(-1), [0,720])
|
|
history.forward()
|
|
assert.notEqual(route.view, settingsView)
|
|
navigateBack()
|
|
assert.equal(route.view, settingsView)
|
|
navigate('/tools')
|
|
navigate('/settings')
|
|
assert.notEqual(route.view, settingsView, 'a new Settings visit has a fresh context')
|
|
const fresh = SettingsPage.setup(props)
|
|
assert.equal(fresh.restoredPage, undefined)
|
|
assert.equal(fresh.state.section, 'lateral')
|
|
|
|
// Search and ordinary Toggles views cannot overwrite each other.
|
|
const searchProps = {...props,initialPage:'standard/following'}
|
|
const search = {...SettingsPage.setup(searchProps),...searchProps}
|
|
search.state.data = {page:'standard/following'}
|
|
SettingsPage.beforeUnmount.call(search)
|
|
assert.equal(route.view.searchPage, 'standard/following')
|
|
const hub = {...SettingsPage.setup(props),...props}
|
|
assert.equal(hub.restoredPage, undefined)
|
|
hub.state.data = {page:'hub'}
|
|
SettingsPage.beforeUnmount.call(hub)
|
|
assert.equal(route.view.searchPage, '')
|
|
const searchAgain = SettingsPage.setup(searchProps)
|
|
assert.equal(searchAgain.restoredPage, undefined, 'a new search after returning to Toggles starts at its requested page')
|
|
console.log('Settings entry, fresh values, search isolation and scroll restoration passed')
|