@bongos/core 1.21.114 → 1.21.116
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/.bongos-core.json +27 -27
- package/docs/copy-inventory.md +420 -418
- package/docs/copy-registry.json +515 -497
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +497 -929
- package/modules/hall-ui/public/deploy.js +2 -0
- package/modules/public-landing/public/projects-hall-live.states.json +0 -5
- package/modules/public-landing/public/projects.html +167 -4
- package/modules/public-landing/public/projects.probes.json +4 -4
- package/modules/public-landing/public/projects.states.json +13 -13
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/upgrade-migrate.js +62 -2
- package/scripts/gds/upgrade-outcome.js +4 -0
- package/scripts/gds/upgrade.js +6 -6
- package/src/module-api.js +1 -1
- package/tests/core_upgrade_runner.mjs +15 -0
- package/tests/instance_db_migrated_on_code_move.mjs +2 -2
- package/tests/projects_hub_pre_uat.mjs +4 -2
- package/tests/upgrade_migrate.mjs +81 -0
- package/tests/wizard_physics.mjs +1 -1
- package/tests/wizard_physics_more.mjs +133 -0
|
@@ -32,7 +32,7 @@ const tenant = { slug: 'charter', db_name: 'charter_db', hosting_shape: 'co-tena
|
|
|
32
32
|
|
|
33
33
|
test('bongos upgrade migrates before it restarts, and only --skip-migrate opts out', () => {
|
|
34
34
|
const src = fs.readFileSync(path.join(ROOT, 'scripts/gds/upgrade.js'), 'utf8');
|
|
35
|
-
const migrate = src.indexOf('const mig = runMigrate(instanceDir, run, opts.migrateAs || null);');
|
|
35
|
+
const migrate = src.indexOf('const mig = await runMigrate(instanceDir, run, opts.migrateAs || null);'); // async since task 1004734 (teed output)
|
|
36
36
|
const restart = src.lastIndexOf('restartService({ service: opts.service, restartCmd: opts.restartCmd }');
|
|
37
37
|
assert.ok(migrate > 0, 'the upgrade flow runs migrate');
|
|
38
38
|
assert.ok(restart > migrate, 'the restart that makes new code live comes AFTER migrate');
|
|
@@ -47,7 +47,7 @@ test('bongos upgrade migrates before it restarts, and only --skip-migrate opts o
|
|
|
47
47
|
// provisioned since task 1003369, and the wrong one cannot connect at all: the project's
|
|
48
48
|
// database is owned by its own Postgres login role with CONNECT revoked from PUBLIC.
|
|
49
49
|
// Asserting the CALL SITE, not just the helper — the helper was never the gap.
|
|
50
|
-
assert.match(src, /const mig = runMigrate\(instanceDir, run, opts\.migrateAs \|\| null\);/,
|
|
50
|
+
assert.match(src, /const mig = await runMigrate\(instanceDir, run, opts\.migrateAs \|\| null\);/,
|
|
51
51
|
'the migrate step is handed --migrate-as, not left on whoever the move happens to be');
|
|
52
52
|
assert.equal(`bash ${CORE_MIGRATE_SCRIPT}`, INSTANCE_MIGRATE_SCRIPT, 'migrate is the instance scaffold script, i.e. the core migrate.sh');
|
|
53
53
|
});
|
|
@@ -701,12 +701,14 @@ test('the rail\'s way-back handlers left with it, the repo picker is honest ARIA
|
|
|
701
701
|
// delegated handlers and their CSS must not linger as dead code
|
|
702
702
|
assert.doesNotMatch(SCRIPT, /railEl|data-goto/, 'no delegated rail handler is left');
|
|
703
703
|
assert.doesNotMatch(COSMOS, /\.steps li\[role=button\]/, 'and no rail-button CSS');
|
|
704
|
-
|
|
704
|
+
// the page's one real listbox is the country field's combobox (task 1004671); the repo
|
|
705
|
+
// picker stays a labelled group of rows
|
|
706
|
+
assert.deepEqual([...HUB.matchAll(/<[^>]*role="listbox"[^>]*>/g)].map((m) => (m[0].match(/id="([^"]+)"/) || [])[1]), ['physCountryList']);
|
|
705
707
|
assert.doesNotMatch(SCRIPT, /role="option"/);
|
|
706
708
|
assert.match(HUB, /<div id="ghList" class="ghlist" role="group" aria-label="Your repositories"><\/div>/, 'the label survives the listbox role');
|
|
707
709
|
assert.match(SCRIPT, /class="ghrow" data-full="' \+ esc\(r\.full_name\) \+ '" aria-current="' \+ \(r\.full_name === sel\) \+ '"/, 'the chosen row is aria-current');
|
|
708
710
|
assert.match(HUB, /\.ghrow\[aria-current=true\]\{/, 'and painted through it');
|
|
709
|
-
assert.doesNotMatch(
|
|
711
|
+
assert.doesNotMatch(SCRIPT.match(/[^\n]*class="ghrow"[^\n]*/)[0], /aria-selected/, 'aria-selected went with the repo picker\'s option role');
|
|
710
712
|
assert.match(SCRIPT, /if \(ev\.key !== 'Escape' \|\| \$\('ghList'\)\.hidden\) return;\n\s*ev\.stopPropagation\(\);\n\s*\$\('ghSearch'\)\.focus\(\);\n\s*closeRepoList\(\);/, 'Escape closes the list after handing focus back');
|
|
711
713
|
assert.match(SCRIPT, /if \(ev\.relatedTarget && !\$\('ghAdopt'\)\.contains\(ev\.relatedTarget\)\) closeRepoList\(\);/, 'focus leaving the picker closes it; a null relatedTarget is left alone');
|
|
712
714
|
assert.match(fn('openCard'), /\$\('cardScrim'\)\.hidden = false;\n\s*pageInert\(true\);\n\s*\$\('cardClose'\)\.focus\(\);/);
|
|
@@ -23,6 +23,9 @@ import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, realpathSync, syml
|
|
|
23
23
|
import { tmpdir } from 'node:os';
|
|
24
24
|
import path from 'node:path';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
|
+
import { EventEmitter } from 'node:events';
|
|
27
|
+
// ESM named imports: knip credits a CJS export only when a test imports it this way.
|
|
28
|
+
import { migrateError, migrateFailureDetail, teeSpawn } from '../scripts/gds/upgrade-migrate.js';
|
|
26
29
|
|
|
27
30
|
const require = createRequire(import.meta.url);
|
|
28
31
|
const { migratePlan, CORE_MIGRATE_SCRIPT } = require('../scripts/gds/upgrade-migrate.js');
|
|
@@ -193,3 +196,81 @@ test('the real fallback command resolves the project as its instance root, despi
|
|
|
193
196
|
const blind = rootsOf(execFileSync('bash', [CORE_MIGRATE_SCRIPT, '--print-roots'], { cwd: dir, env: base, encoding: 'utf8' }));
|
|
194
197
|
assert.notEqual(blind.INSTANCE_ROOT, dir, 'without INIT_CWD pinned, a stray value decides which project is migrated');
|
|
195
198
|
});
|
|
199
|
+
|
|
200
|
+
// ── a failed migrate names its migration and psql's ERROR line (task 1004734) ──────────
|
|
201
|
+
// Seen 2026-10-14: toast's move 1.21.20 → 1.21.115 rolled back and the hub kept only
|
|
202
|
+
// "migrate failed (exit 3)". Which file and which error were in the box's journal alone.
|
|
203
|
+
const FAKE_DSN = ['postgres://app', 'Sup3rS3cretPassw0rd@db.internal/prod'].join(':');
|
|
204
|
+
const MIGRATE_OUT = 'skip core_001_base (already applied)\napply core_998_ok\napply core_999_x\n';
|
|
205
|
+
const PSQL_ERR = `psql:node_modules/@bongos/core/migrations/core_999_x.sql:7: ERROR: check constraint "tasks_kind_check" of relation "tasks" is violated by some row ${FAKE_DSN}\n`;
|
|
206
|
+
|
|
207
|
+
function failingMigrateRun(calls, opts = {}) {
|
|
208
|
+
const base = recordingRun(calls, opts);
|
|
209
|
+
return (cmd, args = [], o = {}) => {
|
|
210
|
+
if ((cmd === 'bash' && args[0] === CORE_MIGRATE_SCRIPT) || (cmd === 'npm' && args[0] === 'run' && args[1] === 'migrate')) {
|
|
211
|
+
calls.push({ line: `${cmd} ${args.join(' ')}`.trim(), opts: o });
|
|
212
|
+
return { status: 3, stdout: MIGRATE_OUT, stderr: PSQL_ERR };
|
|
213
|
+
}
|
|
214
|
+
return base(cmd, args, o);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
test('runMigrate: a failing migration comes back with its file name and psql\'s ERROR line', async () => {
|
|
219
|
+
const mig = await u.runMigrate(adoptedProject(), failingMigrateRun([]));
|
|
220
|
+
assert.equal(mig.ok, false);
|
|
221
|
+
assert.equal(mig.code, 3);
|
|
222
|
+
const error = migrateError(mig);
|
|
223
|
+
assert.match(error, /^migrate failed \(exit 3\)/, 'the prefix upgrade-outcome.js keys on is unchanged');
|
|
224
|
+
assert.match(error, /core_999_x/, 'names the migration that was being applied when it stopped');
|
|
225
|
+
assert.doesNotMatch(error, /core_998_ok/, 'and not an earlier one that applied cleanly');
|
|
226
|
+
assert.match(error, /ERROR: +check constraint "tasks_kind_check" of relation "tasks" is violated by some row/);
|
|
227
|
+
assert.doesNotMatch(error, /Sup3rS3cretPassw0rd/, 'a credential in the error never reaches the hub');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('migrateFailureDetail scrubs secrets and caps the length', () => {
|
|
231
|
+
const d = migrateFailureDetail(`apply core_999_x\nERROR: bad value ${FAKE_DSN} ${'x'.repeat(5000)}\n`);
|
|
232
|
+
assert.doesNotMatch(d, /Sup3rS3cretPassw0rd/);
|
|
233
|
+
assert.ok(d.length <= 400, `capped (${d.length})`);
|
|
234
|
+
assert.match(d, /core_999_x/);
|
|
235
|
+
assert.equal(migrateFailureDetail('npm error code 1\n'), '', 'nothing recognisable adds nothing');
|
|
236
|
+
assert.match(migrateFailureDetail('psql:/x/migrations/core_5_y.sql:3: ERROR: boom\n'), /core_5_y.*ERROR: +boom/, 'psql\'s own file prefix stands in for a missing apply line');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('a full upgrade whose migrate fails reports the migration, the error, and a CHECK-violation class', async () => {
|
|
240
|
+
const dir = adoptedProject({ installed: '1.19.312' });
|
|
241
|
+
const res = await u.runUpgrade(
|
|
242
|
+
{ to: '1.19.1029', registry: true, instance: dir, service: 'mercury', healthCheck: false },
|
|
243
|
+
{
|
|
244
|
+
log: () => {}, err: () => {},
|
|
245
|
+
run: failingMigrateRun([], { dir, installTo: '1.19.1029' }),
|
|
246
|
+
materialize: () => ({ skills: 0 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
247
|
+
databaseUrl: '', pgDatabase: '',
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
assert.equal(res.ok, false);
|
|
251
|
+
assert.match(res.error, /^migrate failed \(exit 3\) at core_999_x: ERROR: +check constraint "tasks_kind_check"/);
|
|
252
|
+
const outcome = require('../scripts/gds/upgrade-outcome.js');
|
|
253
|
+
assert.equal(outcome.upgradeReason(res), 'migrate_check_violation');
|
|
254
|
+
assert.equal(outcome.classifyFailure('migrate_check_violation').class, 'unknown', 'existing rows break the new rule: a person decides, never an unattended retry');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('teeSpawn streams the child\'s output live AND keeps it for the failure report', async () => {
|
|
258
|
+
const pipe = () => Object.assign(new EventEmitter(), { setEncoding() {} });
|
|
259
|
+
let child;
|
|
260
|
+
const spawnImpl = (cmd, args, opts) => {
|
|
261
|
+
child = Object.assign(new EventEmitter(), { stdout: pipe(), stderr: pipe(), opts });
|
|
262
|
+
return child;
|
|
263
|
+
};
|
|
264
|
+
const out = [], err = [];
|
|
265
|
+
const p = teeSpawn('bash', ['m.sh'], { cwd: '/x' }, { spawnImpl, out: { write: (c) => out.push(c) }, err: { write: (c) => err.push(c) } });
|
|
266
|
+
child.stdout.emit('data', 'apply core_999_x\n');
|
|
267
|
+
assert.deepEqual(out, ['apply core_999_x\n'], 'written through as it arrives, not after the child exits');
|
|
268
|
+
child.stderr.emit('data', 'ERROR: boom\n');
|
|
269
|
+
assert.deepEqual(err, ['ERROR: boom\n']);
|
|
270
|
+
child.emit('close', 3);
|
|
271
|
+
const r = await p;
|
|
272
|
+
assert.equal(r.status, 3);
|
|
273
|
+
assert.match(r.output, /apply {2}core_999_x[\s\S]*ERROR: {2}boom/);
|
|
274
|
+
assert.deepEqual(child.opts.stdio, ['inherit', 'pipe', 'pipe']);
|
|
275
|
+
assert.equal(child.opts.cwd, '/x');
|
|
276
|
+
});
|
package/tests/wizard_physics.mjs
CHANGED
|
@@ -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
|
+
});
|