@bongos/core 1.21.113 → 1.21.115

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.
Files changed (265) hide show
  1. package/.bongos-core.json +608 -188
  2. package/.claude/skills/ask-for-help/SKILL.md +1 -1
  3. package/.claude/skills/bongos-feedback/SKILL.md +1 -1
  4. package/.claude/skills/builder-sequence/SKILL.md +1 -1
  5. package/.claude/skills/builder-ship/SKILL.md +1 -0
  6. package/GOVERNMENT.md +128 -11
  7. package/clients/bongos-client/README.md +1 -1
  8. package/clients/bongos-client/bongos-client.global.js +28 -0
  9. package/clients/bongos-client/index.cjs +28 -0
  10. package/clients/bongos-client/index.d.ts +45 -2
  11. package/clients/bongos-client/index.mjs +28 -0
  12. package/docs/adr/0372-an-ask-is-stored-per-docket-matter-and-the-server-refuses-slop.md +33 -0
  13. package/docs/adr/0373-an-ai-session-files-a-blocker-with-an-ask.md +31 -0
  14. package/docs/adr/0374-a-building-lead-is-a-held-position-appointed-by-a-people-matter.md +30 -0
  15. package/docs/adr/0375-who-may-build-without-a-vote-comes-from-the-form-of-government.md +38 -0
  16. package/docs/adr/0376-every-ask-is-routed-to-a-real-seat-on-the-server.md +30 -0
  17. package/docs/adr/0377-governance-is-a-held-position-read-on-me.md +24 -0
  18. package/docs/adr/0378-a-leads-build-outs-are-rows-built-at-once-or-by-a-passed-vote.md +33 -0
  19. package/docs/adr/0379-the-treasury-is-pots-per-goal-a-reserve-and-borrowing-over-real-spend.md +32 -0
  20. package/docs/adr/0380-a-strictness-preset-is-a-bundle-of-laws-switched-by-one-laws-vote.md +26 -0
  21. package/docs/adr/README.md +10 -1
  22. package/docs/api/openapi.json +989 -87
  23. package/docs/api-reference.md +18 -4
  24. package/docs/copy-inventory.md +709 -626
  25. package/docs/copy-registry.json +1673 -858
  26. package/docs/file-map.md +2 -1
  27. package/docs/module-api-changelog.md +4 -0
  28. package/docs/packs/engineer.md +2 -1
  29. package/docs/page-inventory.json +91 -6
  30. package/docs/page-readings.json +4875 -4748
  31. package/modules/builder-settings/law-locks.js +202 -0
  32. package/modules/builder-settings/routes/settings.js +66 -6
  33. package/modules/builder-settings/settings.js +7 -0
  34. package/modules/discord/ship-broadcast.js +22 -13
  35. package/modules/government/asks-db.js +54 -0
  36. package/modules/government/asks.js +269 -0
  37. package/modules/government/board-idea-reads.js +68 -0
  38. package/modules/government/board.js +21 -57
  39. package/modules/government/build-rights.js +148 -0
  40. package/modules/government/buildouts.js +160 -0
  41. package/modules/government/counts.js +81 -0
  42. package/modules/government/db.js +242 -0
  43. package/modules/government/decision-board.js +234 -8
  44. package/modules/government/decision-rules.js +2 -0
  45. package/modules/government/docket.js +114 -6
  46. package/modules/government/law-board.js +131 -0
  47. package/modules/government/laws.js +241 -0
  48. package/modules/government/leads.js +170 -0
  49. package/modules/government/migrations/government_027_laws.sql +143 -0
  50. package/modules/government/migrations/government_028_asks.sql +57 -0
  51. package/modules/government/migrations/government_029_leads.sql +135 -0
  52. package/modules/government/migrations/government_030_buildouts.sql +111 -0
  53. package/modules/government/migrations/government_031_treasury.sql +107 -0
  54. package/modules/government/module.json +2 -1
  55. package/modules/government/pollers/treasury-borrow.js +62 -0
  56. package/modules/government/positions.js +107 -0
  57. package/modules/government/presets.js +149 -0
  58. package/modules/government/routes/government.js +425 -2
  59. package/modules/government/routing.js +154 -0
  60. package/modules/government/treasury-db.js +166 -0
  61. package/modules/government/treasury.js +241 -0
  62. package/modules/hall-ui/public/atlas.html +2 -2
  63. package/modules/hall-ui/public/blockers.html +2 -2
  64. package/modules/hall-ui/public/board-room.html +2 -2
  65. package/modules/hall-ui/public/board-room.js +2 -0
  66. package/modules/hall-ui/public/city-draw.js +604 -11
  67. package/modules/hall-ui/public/city.css +157 -1
  68. package/modules/hall-ui/public/city.html +37 -7
  69. package/modules/hall-ui/public/city.js +441 -21
  70. package/modules/hall-ui/public/city.states.json +110 -5
  71. package/modules/hall-ui/public/collab.html +2 -2
  72. package/modules/hall-ui/public/commands.html +2 -2
  73. package/modules/hall-ui/public/copy-desk.html +2 -2
  74. package/modules/hall-ui/public/craft-mode.js +112 -29
  75. package/modules/hall-ui/public/deploy.html +2 -2
  76. package/modules/hall-ui/public/diagram-viewer.css +12 -0
  77. package/modules/hall-ui/public/diagram-viewer.js +4 -0
  78. package/modules/hall-ui/public/diagrams.html +2 -2
  79. package/modules/hall-ui/public/drachmae.html +2 -2
  80. package/modules/hall-ui/public/fleet.html +2 -2
  81. package/modules/hall-ui/public/gate.html +2 -2
  82. package/modules/hall-ui/public/goal-map.html +2 -2
  83. package/modules/hall-ui/public/goals.html +2 -2
  84. package/modules/hall-ui/public/gov-feeds.js +141 -0
  85. package/modules/hall-ui/public/gov-living.js +1002 -0
  86. package/modules/hall-ui/public/gov-positions.js +97 -0
  87. package/modules/hall-ui/public/government.html +2 -2
  88. package/modules/hall-ui/public/hall-render.js +3 -0
  89. package/modules/hall-ui/public/idea.html +2 -2
  90. package/modules/hall-ui/public/ideas.html +2 -2
  91. package/modules/hall-ui/public/index.html +3 -3
  92. package/modules/hall-ui/public/modules.html +2 -2
  93. package/modules/hall-ui/public/my-agents.css +334 -0
  94. package/modules/hall-ui/public/my-agents.html +72 -0
  95. package/modules/hall-ui/public/my-agents.js +603 -0
  96. package/modules/hall-ui/public/my-agents.states.json +37 -0
  97. package/modules/hall-ui/public/primer.html +2 -2
  98. package/modules/hall-ui/public/profile-connections.js +40 -28
  99. package/modules/hall-ui/public/profile.html +2 -2
  100. package/modules/hall-ui/public/project-settings.html +2 -2
  101. package/modules/hall-ui/public/ranks.html +2 -2
  102. package/modules/hall-ui/public/roadmap.html +2 -2
  103. package/modules/hall-ui/public/room.css +8 -0
  104. package/modules/hall-ui/public/roster.html +2 -2
  105. package/modules/hall-ui/public/sessions.html +2 -2
  106. package/modules/hall-ui/public/settings.html +2 -2
  107. package/modules/hall-ui/public/shell.js +116 -16
  108. package/modules/hall-ui/public/street-card.js +18 -1
  109. package/modules/hall-ui/public/street-deck.css +299 -0
  110. package/modules/hall-ui/public/street-deck.js +1145 -0
  111. package/modules/hall-ui/public/street-depth.js +712 -0
  112. package/modules/hall-ui/public/street-desk.js +5 -2
  113. package/modules/hall-ui/public/street-draw.js +109 -16
  114. package/modules/hall-ui/public/street-inside.js +137 -22
  115. package/modules/hall-ui/public/street-office.js +705 -0
  116. package/modules/hall-ui/public/street.css +161 -11
  117. package/modules/hall-ui/public/street.html +41 -11
  118. package/modules/hall-ui/public/street.js +78 -66
  119. package/modules/hall-ui/public/street.states.json +129 -21
  120. package/modules/hall-ui/public/studio.html +2 -2
  121. package/modules/hall-ui/public/style.css +5 -1
  122. package/modules/hall-ui/public/task.html +2 -2
  123. package/modules/hall-ui/public/thinking.html +2 -2
  124. package/modules/hall-ui/public/tweak-editor.html +2 -2
  125. package/modules/hall-ui/public/watch.html +2 -2
  126. package/modules/hall-ui/public/watch.js +14 -1
  127. package/modules/hall-ui/public/work.html +2 -2
  128. package/modules/hall-ui/records/living-city.md +57 -0
  129. package/modules/hall-ui/records/my-agents.md +21 -0
  130. package/modules/hall-ui/records/oversight-family.md +1 -0
  131. package/modules/hall-ui/records/profile-connections.md +10 -0
  132. package/modules/hall-ui/records/reading-rooms.md +2 -0
  133. package/modules/ideas/routes/blockers.js +92 -3
  134. package/modules/lifecycle/module.json +2 -1
  135. package/modules/lifecycle/moving.js +17 -1
  136. package/modules/lifecycle/routes/moving.js +6 -0
  137. package/modules/lifecycle/routes/project-settings.js +23 -0
  138. package/modules/onboarding/approval-broadcast.js +8 -0
  139. package/modules/onboarding/routes/access-requests.js +16 -6
  140. package/modules/platform-identity/connections.js +13 -2
  141. package/modules/platform-identity/routes/connections.js +7 -1
  142. package/modules/public-landing/public/account-private.states.json +2 -2
  143. package/modules/public-landing/public/account.html +65 -5
  144. package/modules/public-landing/public/account.states.json +3 -3
  145. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.jpg.json +1 -1
  146. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp +0 -0
  147. package/modules/public-landing/public/assets/world/r2-bongos-chrome-b.webp.json +13 -0
  148. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.jpg.json +1 -1
  149. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp +0 -0
  150. package/modules/public-landing/public/assets/world/r2-chrome-blob-one.webp.json +13 -0
  151. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.jpg.json +1 -1
  152. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp +0 -0
  153. package/modules/public-landing/public/assets/world/r2-chrome-blobs-set.webp.json +13 -0
  154. package/modules/public-landing/public/assets/world.css +4 -4
  155. package/modules/public-landing/public/connections-empty.states.json +14 -0
  156. package/modules/public-landing/public/connections-inactive.states.json +14 -0
  157. package/modules/public-landing/public/connections.html +450 -0
  158. package/modules/public-landing/public/connections.probes.json +414 -0
  159. package/modules/public-landing/public/connections.states.json +17 -0
  160. package/modules/public-landing/public/contact.html +2 -2
  161. package/modules/public-landing/public/guild.html +2 -2
  162. package/modules/public-landing/public/index.html +2 -2
  163. package/modules/public-landing/public/invitations.html +2 -2
  164. package/modules/public-landing/public/privacy.html +2 -2
  165. package/modules/public-landing/public/projects-hall-live.states.json +0 -5
  166. package/modules/public-landing/public/projects.html +169 -6
  167. package/modules/public-landing/public/projects.probes.json +10 -6
  168. package/modules/public-landing/public/projects.states.json +13 -13
  169. package/modules/public-landing/public/terms.html +2 -2
  170. package/modules/ui-design/kit/fixtures/connections-empty.json +12 -0
  171. package/modules/ui-design/kit/fixtures/connections-inactive.json +7 -0
  172. package/modules/ui-design/kit/fixtures/connections.json +22 -0
  173. package/modules/ui-design/kit/serve.js +95 -0
  174. package/package-lock.json +2 -2
  175. package/package.json +1 -1
  176. package/release-notes.json +128 -0
  177. package/scripts/discord/README.md +8 -5
  178. package/scripts/discord/post.js +12 -1
  179. package/scripts/gds/artifact-audit.js +20 -3
  180. package/scripts/gds/ask.js +164 -0
  181. package/scripts/gds/autobongos-verify.js +21 -2
  182. package/scripts/gds/government-walk.js +638 -4
  183. package/scripts/gds/pulse.js +9 -6
  184. package/scripts/gds/ship-escalation.js +22 -3
  185. package/scripts/gds/ship-finish.js +5 -4
  186. package/scripts/gds/ship-merge-gate.js +18 -6
  187. package/scripts/hall-preview/README.md +41 -1
  188. package/scripts/hall-preview/pref-writes.js +140 -0
  189. package/scripts/hall-preview/refresh-fixtures.js +101 -7
  190. package/scripts/hall-preview/scrub-identity.js +366 -7
  191. package/scripts/hall-preview/server.js +27 -1
  192. package/scripts/render-diagrams.sh +4 -2
  193. package/src/bongos/auth-idp.js +6 -1
  194. package/src/bongos/project-settings.js +41 -0
  195. package/src/bongos/route-rank-check.js +27 -0
  196. package/src/bongos/routes/me.js +68 -7
  197. package/src/bongos/serve-internal.js +5 -1
  198. package/src/module-api.js +7 -1
  199. package/src/platform-server.js +10 -0
  200. package/tests/application_lifecycle.mjs +47 -5
  201. package/tests/conductor_main_e2e.mjs +21 -0
  202. package/tests/connections_api.mjs +18 -0
  203. package/tests/craft_mode_nav.mjs +5 -3
  204. package/tests/craft_mode_tallies.mjs +52 -16
  205. package/tests/discord_approvals.mjs +11 -0
  206. package/tests/discord_channels.mjs +20 -0
  207. package/tests/fixtures/gov-seated.mjs +64 -0
  208. package/tests/fixtures/government-ask-cards.json +1284 -0
  209. package/tests/government_act_alone_buildings.mjs +1 -0
  210. package/tests/government_agent_asks.mjs +322 -0
  211. package/tests/government_asks.mjs +408 -0
  212. package/tests/government_build_rights.mjs +253 -0
  213. package/tests/government_buildouts.mjs +382 -0
  214. package/tests/government_counts.mjs +143 -0
  215. package/tests/government_decision_rules.mjs +5 -1
  216. package/tests/government_laws.mjs +488 -0
  217. package/tests/government_laws_session.mjs +321 -0
  218. package/tests/government_leads.mjs +375 -0
  219. package/tests/government_positions.mjs +127 -0
  220. package/tests/government_presets.mjs +215 -0
  221. package/tests/government_routes.mjs +8 -1
  222. package/tests/government_routing.mjs +118 -0
  223. package/tests/government_seed.mjs +5 -0
  224. package/tests/government_treasury.mjs +394 -0
  225. package/tests/governor_docket.mjs +26 -2
  226. package/tests/hall_buildouts.mjs +208 -0
  227. package/tests/hall_city.mjs +14 -6
  228. package/tests/hall_city_living.mjs +617 -0
  229. package/tests/hall_gov_counts.mjs +336 -0
  230. package/tests/hall_gov_living.mjs +636 -0
  231. package/tests/hall_gov_real_site.mjs +655 -0
  232. package/tests/hall_matter_card.mjs +6 -1
  233. package/tests/hall_my_agents.mjs +481 -0
  234. package/tests/hall_nav.mjs +107 -4
  235. package/tests/hall_page_gate_map.mjs +4 -0
  236. package/tests/hall_preview_governor_fixtures.mjs +30 -3
  237. package/tests/hall_preview_scrub.mjs +59 -1
  238. package/tests/hall_preview_security.mjs +169 -0
  239. package/tests/hall_reading_world.mjs +44 -0
  240. package/tests/hall_roster_world.mjs +6 -2
  241. package/tests/hall_street.mjs +18 -2
  242. package/tests/hall_street_amend.mjs +1 -1
  243. package/tests/hall_street_deck.mjs +609 -0
  244. package/tests/hall_street_depth.mjs +559 -0
  245. package/tests/hall_street_desk.mjs +1 -1
  246. package/tests/hall_street_inside.mjs +29 -5
  247. package/tests/hall_tweak_editor.mjs +4 -1
  248. package/tests/hub_account_page.mjs +94 -3
  249. package/tests/hub_connections_page.mjs +507 -0
  250. package/tests/hub_invite_badge.mjs +1 -1
  251. package/tests/idp_device_exchange.mjs +18 -0
  252. package/tests/landing_legal_pages.mjs +8 -5
  253. package/tests/landing_page.mjs +23 -4
  254. package/tests/nav_permission_atoms.mjs +4 -1
  255. package/tests/profile_connections_ui.mjs +88 -25
  256. package/tests/projects_hub.mjs +15 -0
  257. package/tests/projects_hub_pre_uat.mjs +4 -2
  258. package/tests/render_check.mjs +2 -2
  259. package/tests/ship_broadcast_routing.mjs +57 -16
  260. package/tests/ship_broadcast_server.mjs +40 -7
  261. package/tests/ship_merge_gate.mjs +19 -0
  262. package/tests/ui_design_kit.mjs +2 -2
  263. package/tests/watch_applications_queue.mjs +23 -0
  264. package/tests/wizard_physics.mjs +1 -1
  265. package/tests/wizard_physics_more.mjs +133 -0
@@ -470,6 +470,29 @@ await test('NO profile is not an error — the row renders exactly as it always
470
470
  assert.match(row.textContent, /octocat/);
471
471
  });
472
472
 
473
+ // The handle the vouch carried (ADR 0333 D5, task 1004673): when the hub is down the
474
+ // live profile is null, and the stored handle is what the queue can still call them.
475
+ await test('a vouched row with NO profile names the platform handle its vouch carried', async () => {
476
+ const h = await boot({ rows: { pending: withProfile(null, { applicant_handle: 'mona-h' }), invited: [], dismissed: [] } });
477
+ const facts = h.rows()[0].querySelector('.ov-row__facts');
478
+ assert.match(facts.textContent, /On the platform as @mona-h/);
479
+ assert.equal(facts.querySelectorAll('a').length, 0, 'plain text: the hub has no person page to point at');
480
+ });
481
+
482
+ await test('the handle stays off a row that drew its profile, and off an unvouched row', async () => {
483
+ const drew = await boot({ rows: { pending: withProfile(PUBLIC_PROFILE, { applicant_handle: 'mona-h' }), invited: [], dismissed: [] } });
484
+ assert.doesNotMatch(drew.rows()[0].textContent, /On the platform as/, 'the live record is shown instead');
485
+ const unvouched = await boot({ rows: { pending: withProfile(null, { vouched: false, applicant_handle: 'mona-h' }), invited: [], dismissed: [] } });
486
+ assert.doesNotMatch(unvouched.rows()[0].textContent, /On the platform as/, 'a name anyone could type has no vouched handle');
487
+ });
488
+
489
+ await test('the vouched handle is escaped', async () => {
490
+ const h = await boot({ rows: { pending: withProfile(null, { applicant_handle: '<b>x</b>' }), invited: [], dismissed: [] } });
491
+ const facts = h.rows()[0].querySelector('.ov-row__facts');
492
+ assert.equal(facts.querySelectorAll('b').length, 0);
493
+ assert.match(facts.textContent, /@<b>x<\/b>/);
494
+ });
495
+
473
496
  await test('an unrecognised view shape draws nothing rather than guessing', async () => {
474
497
  const h = await boot({ rows: { pending: withProfile({ view: 'everything', account: { bio: 'x' } }), invited: [], dismissed: [] } });
475
498
  assert.equal(h.rows()[0].querySelector('.ov-row__profile'), null,
@@ -126,7 +126,7 @@ test('every choice the questions offer is an answer the server accepts', () => {
126
126
  const checks = HUB.slice(HUB.indexOf('id="checksMain"'), HUB.indexOf('id="checksStop"'));
127
127
  assert.deepEqual([...new Set([...checks.matchAll(/data-phys="([a-z_]+)"/g)].map((m) => m[1]))], ['minors_on_board', 'builder_geography', 'entity'],
128
128
  'the quick checks ask about under-18s, where builders will be and who is behind it in the mockup’s order');
129
- assert.match(checks, /<select id="physCountry">/, 'and where you are');
129
+ assert.match(checks, /<select id="physCountry" aria-labelledby="physCountryLbl">/, 'and where you are (the select the searchable list picks for, task 1004671)');
130
130
  const seen = [...panel.matchAll(/data-phys="([a-z_]+)" data-val="([^"]+)"/g)];
131
131
  assert.ok(seen.length >= 20, 'the questions\' choices');
132
132
  for (const [, key, raw] of seen) {
@@ -608,3 +608,136 @@ test('the address is on every path, the demo\'s too: never a fixed row, always c
608
608
  assert.equal(w.run('firstInvalidRow()'), null, `${label}: then Create goes on`);
609
609
  }
610
610
  });
611
+
612
+ // ── "Where are you?" as a searchable list (task 1004671) ─────────────────────
613
+ // The combobox only picks for the hidden select, so everything that reads the select
614
+ // (the region, the screen, the draft, the fill, what Create sends) is the same path.
615
+ const key = (k) => ({ key: k, preventDefault() {}, stopPropagation() {} });
616
+ function typeCountry(w, text) {
617
+ w.$('physCountryIn').value = text;
618
+ w.$('physCountryIn').listeners.input();
619
+ }
620
+ const shownCodes = (w) => plain(w.run('cboxShown.map(function (i) { return i.code; })'));
621
+
622
+ test('the country starts at the United States, in a combobox in front of the select', () => {
623
+ const w = boot();
624
+ w.run('physDefaults(); paintChecks();');
625
+ assert.equal(w.a().home_country, 'US', 'the default answer is the United States');
626
+ assert.equal(w.$('physCountry').value, 'US', 'on the select everything reads');
627
+ assert.equal(w.$('physCountryIn').value, 'United States', 'and said by name in the field');
628
+ assert.equal(w.$('physRegionField').hidden, false, 'so the state field is there from the start');
629
+ assert.equal(w.$('physCountry').hidden, true, 'the script hides the plain select');
630
+ assert.equal(w.$('physCountryBox').hidden, false, 'and shows the searchable field');
631
+ const input = w.$('physCountryIn'), list = w.$('physCountryList');
632
+ assert.equal(input.getAttribute('role'), 'combobox');
633
+ assert.equal(input.getAttribute('aria-controls'), 'physCountryList');
634
+ assert.equal(input.getAttribute('aria-expanded'), 'false');
635
+ assert.equal(list.getAttribute('role'), 'listbox');
636
+ assert.equal(list.hidden, true, 'closed until asked');
637
+ // a choice taken back stays taken back, and the last project's answers still win
638
+ const cleared = boot();
639
+ cleared.run('phys().answers.home_country = null; physDefaults();');
640
+ assert.equal(cleared.a().home_country, null);
641
+ const filled = boot({ draft: {} });
642
+ filled.run('phys(); physDefaults(); physAccountAnswers = { home_country: "NL" };');
643
+ filled.$('physFromAccount').click();
644
+ assert.equal(filled.a().home_country, 'NL', 'the fill replaces the default');
645
+ assert.equal(filled.$('physCountryIn').value, 'Netherlands');
646
+ });
647
+
648
+ test('typing filters the list, ignoring case and accents, the closest names first', () => {
649
+ const w = boot();
650
+ w.run('physDefaults(); paintChecks();');
651
+ w.$('physCountryIn').listeners.click();
652
+ assert.equal(w.$('physCountryList').hidden, false, 'a click opens the whole list');
653
+ assert.equal(w.$('physCountryIn').getAttribute('aria-expanded'), 'true');
654
+ assert.ok(shownCodes(w).length > 200, 'every country');
655
+ assert.equal(w.$('physCountryIn').getAttribute('aria-activedescendant'), 'physCountryOpt-US', 'on the one already chosen');
656
+ typeCountry(w, 'NETH');
657
+ assert.deepEqual(shownCodes(w), ['NL', 'BQ'], 'case does not matter; the name that starts with it first, then Caribbean Netherlands');
658
+ typeCountry(w, 'cote');
659
+ assert.deepEqual(shownCodes(w), ['CI'], 'nor accents (Côte d’Ivoire)');
660
+ typeCountry(w, 'curacao');
661
+ assert.deepEqual(shownCodes(w), ['CW']);
662
+ typeCountry(w, 'ger');
663
+ assert.equal(shownCodes(w)[0], 'DE', 'a name that starts with it leads');
664
+ assert.ok(shownCodes(w).includes('NG') && shownCodes(w).includes('DZ'), 'then names with it inside');
665
+ typeCountry(w, 'usa');
666
+ assert.equal(shownCodes(w)[0], 'US');
667
+ typeCountry(w, 'zzz');
668
+ assert.deepEqual(shownCodes(w), []);
669
+ assert.equal(w.$('physCountryNone').hidden, false, 'nothing matches, and it says so');
670
+ assert.equal(w.$('physCountryIn').getAttribute('aria-activedescendant'), null);
671
+ // every option is a node with its name as text, never markup
672
+ const opts = w.$('physCountryList').children.filter((c) => c.getAttribute('data-code'));
673
+ assert.ok(opts.length > 200);
674
+ const nl = opts.find((c) => c.getAttribute('data-code') === 'NL');
675
+ assert.equal(nl.textContent, 'Netherlands');
676
+ assert.equal(nl.getAttribute('role'), 'option');
677
+ assert.equal(nl.getAttribute('id'), 'physCountryOpt-NL');
678
+ assert.doesNotMatch(SCRIPT.slice(SCRIPT.indexOf('function cboxFold('), SCRIPT.indexOf('function physCountryChanged(')), /innerHTML/);
679
+ });
680
+
681
+ test('arrow keys move through the list, Enter picks, Escape puts the country back', () => {
682
+ const w = boot();
683
+ w.run('physDefaults(); paintChecks();');
684
+ const input = w.$('physCountryIn');
685
+ typeCountry(w, 'ger');
686
+ assert.equal(input.getAttribute('aria-activedescendant'), 'physCountryOpt-DE');
687
+ input.listeners.keydown(key('ArrowDown'));
688
+ const second = shownCodes(w)[1];
689
+ assert.equal(input.getAttribute('aria-activedescendant'), 'physCountryOpt-' + second);
690
+ input.listeners.keydown(key('ArrowUp'));
691
+ assert.equal(input.getAttribute('aria-activedescendant'), 'physCountryOpt-DE');
692
+ input.listeners.keydown(key('Enter'));
693
+ assert.equal(w.a().home_country, 'DE', 'Enter picks');
694
+ assert.equal(w.$('physCountry').value, 'DE');
695
+ assert.equal(input.value, 'Germany');
696
+ assert.equal(w.$('physCountryList').hidden, true, 'and closes the list');
697
+ assert.equal(input.getAttribute('aria-expanded'), 'false');
698
+ typeCountry(w, 'fra');
699
+ input.listeners.keydown(key('Escape'));
700
+ assert.equal(w.a().home_country, 'DE', 'Escape changes nothing');
701
+ assert.equal(input.value, 'Germany', 'and puts the chosen name back');
702
+ assert.equal(w.$('physCountryList').hidden, true);
703
+ input.listeners.keydown(key('ArrowDown'));
704
+ assert.equal(w.$('physCountryList').hidden, false, 'ArrowDown opens it again');
705
+ assert.equal(input.getAttribute('aria-activedescendant'), 'physCountryOpt-DE');
706
+ typeCountry(w, 'France');
707
+ input.listeners.blur();
708
+ assert.equal(w.a().home_country, 'FR', 'leaving with a whole name typed picks it');
709
+ typeCountry(w, 'fr');
710
+ input.listeners.blur();
711
+ assert.equal(w.a().home_country, 'FR', 'leaving half way keeps the country');
712
+ assert.equal(input.value, 'France');
713
+ });
714
+
715
+ test('a country picked from the list runs the select\'s own path: the region, the screen and what Create sends', async () => {
716
+ const w = boot();
717
+ w.run('physDefaults(); paintChecks();');
718
+ w.a().home_region = 'US-CA';
719
+ typeCountry(w, 'canada');
720
+ const ca = w.$('physCountryList').children.find((c) => c.getAttribute('data-code') === 'CA');
721
+ ca.click();
722
+ assert.equal(w.a().home_country, 'CA');
723
+ assert.equal(w.a().home_region, null, 'a state of another country is dropped');
724
+ assert.equal(w.$('physRegionField').hidden, false, 'a country with provinces asks for one');
725
+ assert.ok(w.$('physRegion').children.some((o) => o.value === 'CA-ON'));
726
+ typeCountry(w, 'netherlands');
727
+ w.$('physCountryList').children.find((c) => c.getAttribute('data-code') === 'NL').click();
728
+ assert.equal(w.$('physRegionField').hidden, true, 'and one without does not');
729
+ await w.settle();
730
+ const asked = w.fetched.filter((f) => f.url.includes('physics-check')).map((f) => f.body.detail.home_country);
731
+ assert.deepEqual(asked, ['CA', 'NL'], 'each pick asks the table again, with the code');
732
+ // the same code the select path sends
733
+ const viaSelect = boot();
734
+ viaSelect.run('physDefaults(); paintChecks();');
735
+ viaSelect.$('physCountry').value = 'NL';
736
+ viaSelect.$('physCountry').listeners.change();
737
+ assert.deepEqual(plain(w.run('createDetail()')), plain(viaSelect.run('createDetail()')), 'Create sends exactly what the select sends');
738
+ assert.equal(plain(w.run('createDetail()')).home_country, 'NL');
739
+ // and untouched, Create sends the default
740
+ const untouched = boot();
741
+ untouched.run('physDefaults(); paintChecks();');
742
+ assert.equal(plain(untouched.run('createDetail()')).home_country, 'US');
743
+ });