@bongos/core 1.19.929 → 1.19.931

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/tests/helpers.mjs CHANGED
@@ -855,3 +855,124 @@ export function bootThinkingPage({ pages = [], viewer = { id: 95 }, meStatus = 2
855
855
  filter: (id) => { tabState.active = id; if (tabState.onChange) tabState.onChange(id); },
856
856
  };
857
857
  }
858
+
859
+ // bootSkyPage — boot the REAL modules/hall-ui/public/sky.js in a vm against a
860
+ // stub DOM, and hand back the field, the panel and the legend it wrote plus
861
+ // every request it made.
862
+ //
863
+ // The fourth sibling of bootProfilePage / bootIdeaPage / bootThinkingPage, and
864
+ // here for the reason the third one is: THE SKY (task 1004105 / BV2.R26) is a
865
+ // DRAWING of facts that are all correct upstream, and the only place it can go
866
+ // wrong is the last step. A glyph is geometry, so the questions this can ask
867
+ // that no server test can are: does a remnant keep its diameter and lose its
868
+ // fill, does an unreadable brightness leave a star lit, does the page draw a
869
+ // second compression curve of its own, and does every thought carry its whole
870
+ // sentence for a reader who cannot see the picture.
871
+ //
872
+ // THE REAL idea-objects.js IS LOADED INTO THE SAME SANDBOX, never stubbed —
873
+ // the same rule bootThinkingPage keeps, and for the sharper reason here: the
874
+ // pair exists so the sky and the room cannot call one object two names, and a
875
+ // suite that stubbed the vocabulary would assert the sky says whatever the
876
+ // suite told it to.
877
+ const SKY_JS = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/sky.js'), 'utf8');
878
+ export function bootSkyPage({
879
+ pages = [], viewer = { id: 95 }, meStatus = 200, trailStatus = 200,
880
+ goals = null, criteria = {},
881
+ } = {}) {
882
+ const els = new Map();
883
+ const listeners = new Map();
884
+ const makeEl = (id) => ({
885
+ dataset: {}, style: {}, textContent: '', hidden: false, _html: '',
886
+ classList: { add() {}, remove() {}, toggle() { return false; }, contains() { return false; } },
887
+ setAttribute() {}, getAttribute() { return null; },
888
+ addEventListener(type, fn) { listeners.set(`${id}:${type}`, fn); },
889
+ querySelector() { return null; }, querySelectorAll() { return []; }, appendChild() {},
890
+ set innerHTML(v) { this._html = String(v); }, get innerHTML() { return this._html; },
891
+ });
892
+ const documentObj = {
893
+ title: '',
894
+ body: { dataset: { page: 'sky' } },
895
+ documentElement: { dataset: {}, style: {}, classList: { add() {}, remove() {} } },
896
+ getElementById(id) { if (!els.has(id)) els.set(id, makeEl(id)); return els.get(id); },
897
+ querySelector(sel) { return this.getElementById(String(sel).replace(/^#/, '')); },
898
+ querySelectorAll() { return []; },
899
+ createElement() { return makeEl('new'); },
900
+ addEventListener() {},
901
+ };
902
+
903
+ const requests = [];
904
+ const client = {
905
+ request: async (method, url) => {
906
+ requests.push(url);
907
+ if (/\/me$/.test(url)) {
908
+ return meStatus === 200
909
+ ? { status: 200, ok: true, data: { builder: viewer } }
910
+ : { status: meStatus, ok: false, data: null };
911
+ }
912
+ // The two well reads. `goals: null` is a route that could not be read at
913
+ // all, which must leave the wells named by their id rather than blank;
914
+ // `criteria` absent for a goal is "not read", which must leave that well
915
+ // with no rings rather than zero of them.
916
+ const one = /\/goals\/(\d+)$/.exec(url);
917
+ if (one) {
918
+ const list = criteria[one[1]];
919
+ return list === undefined
920
+ ? { status: 404, ok: false, data: null }
921
+ : { status: 200, ok: true, data: { goal: { id: one[1] }, criteria: list } };
922
+ }
923
+ if (/\/goals$/.test(url)) {
924
+ return goals === null
925
+ ? { status: 500, ok: false, data: null }
926
+ : { status: 200, ok: true, data: { goals } };
927
+ }
928
+ if (trailStatus !== 200) return { status: trailStatus, ok: false, data: null };
929
+ const offset = Number((/offset=(\d+)/.exec(url) || [])[1] || 0);
930
+ const page = pages.find((p) => Number(p.offset || 0) === offset) || { ideas: [], total: 0 };
931
+ return { status: 200, ok: true, data: page };
932
+ },
933
+ };
934
+
935
+ const windowObj = {
936
+ __BRANDING__: {}, __MODULES__: { enabled: {} },
937
+ BongosClient: { createClient: () => client },
938
+ CSS: { escape: (s) => String(s) },
939
+ OTB: {
940
+ escapeHtml: (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])),
941
+ $: (sel) => documentObj.querySelector(sel),
942
+ fmtDate: (iso) => String(iso ?? '').slice(0, 10),
943
+ toast() {},
944
+ },
945
+ location: { hostname: 'builders.cloudbongos.com', search: '', pathname: '/sky', hash: '', assign() {} },
946
+ localStorage: { getItem: () => null, setItem() {} },
947
+ addEventListener() {},
948
+ };
949
+
950
+ const sandbox = {
951
+ window: windowObj, document: documentObj, location: windowObj.location,
952
+ console, setTimeout, clearTimeout, URLSearchParams, Math, Number,
953
+ };
954
+ sandbox.globalThis = sandbox;
955
+ vm.createContext(sandbox);
956
+ vm.runInContext(IDEA_OBJECTS_JS, sandbox, { filename: 'idea-objects.js' });
957
+ vm.runInContext(SKY_JS, sandbox, { filename: 'sky.js' });
958
+
959
+ return {
960
+ requests,
961
+ objects: () => windowObj.OTBIdeaObjects,
962
+ field: () => documentObj.getElementById('sky-map').innerHTML,
963
+ panel: () => documentObj.getElementById('sky-panel').innerHTML,
964
+ legend: () => documentObj.getElementById('sky-legend').innerHTML,
965
+ sub: () => documentObj.getElementById('sky-sub').textContent,
966
+ foot: () => documentObj.getElementById('sky-foot'),
967
+ // Picking is the page's only control, and it is DELEGATED — one listener on
968
+ // the field for a trail of 300 bodies — so the suite drives it the way a
969
+ // click does rather than by calling an internal the page does not export.
970
+ pick: (ideaId) => {
971
+ const fn = listeners.get('sky-map:click');
972
+ if (!fn) throw new Error('the field wired no click listener');
973
+ fn({ target: { closest: (sel) => (sel === '[data-idea]'
974
+ ? { getAttribute: () => String(ideaId) }
975
+ : null) } });
976
+ },
977
+ };
978
+ }