ui: the Data Connection event gains the date-time object; the "more" flag is precise
- The 0x1D event form now offers the Date-Time and Time zone object (TS 102 223 8.39): an optional select that includes the host clock's date-time, built by eventDateTimeTlv() as seven semi-octet BCD bytes in the TS 23.040 9.2.3.11 order (units high, tens low) with the time zone in quarters of an hour and its sign in bit 3 of the first semi-octet. - The Cards list's empty-state colspan is 8 now (the table has had 8 columns since the keyset refactor, not 10). - The expanded-registry "(first page only)" note marks only an unfulfilled 6310: ramExpandedMore() keeps the promise set when the follow-up page was refused/failed or the page cap stopped the paging, and clears it when an accepted page ended the listing (a completed multi-page listing was annotated wrongly before). Tests: eventDateTimeTlv (fixed dates, both timezone signs), the 0x1D build with and without the date-time, and ramExpandedMore's cases. Help EN/RU and both AGENTS files; sw cache simple-v347.
This commit is contained in:
@@ -504,7 +504,7 @@
|
|||||||
<li><strong>Location Status</strong> — выпадающий список: Normal / Limited / No service (тег <code class="font-mono text-sm">9B</code>)</li>
|
<li><strong>Location Status</strong> — выпадающий список: Normal / Limited / No service (тег <code class="font-mono text-sm">9B</code>)</li>
|
||||||
<li><strong>Access Technology Change</strong> — 13 типов RAT (тег <code class="font-mono text-sm">BF</code>)</li>
|
<li><strong>Access Technology Change</strong> — 13 типов RAT (тег <code class="font-mono text-sm">BF</code>)</li>
|
||||||
<li><strong>Network Rejection</strong> — полная адаптивная форма: тип регистрации (LU / GPRS / EPS / 5GS), поля местоположения (MCC, MNC, LAC, RAC, TAC), технология доступа и единый выпадающий список из 53 кодов причин (EMM, GMM, 5GMM и LU)</li>
|
<li><strong>Network Rejection</strong> — полная адаптивная форма: тип регистрации (LU / GPRS / EPS / 5GS), поля местоположения (MCC, MNC, LAC, RAC, TAC), технология доступа и единый выпадающий список из 53 кодов причин (EMM, GMM, 5GMM и LU)</li>
|
||||||
<li><strong>Data Connection Status Change</strong> (0x1D) — статус (successful / rejected / dropped-deactivated), тип соединения (PDP / PDN / PDU), необязательная причина (E/5G)SM, transaction identifier и location status, а также необязательные access technology, APN, тип PDP/PDN/PDU и location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
<li><strong>Data Connection Status Change</strong> (0x1D) — статус (successful / rejected / dropped-deactivated), тип соединения (PDP / PDN / PDU), необязательная причина (E/5G)SM, transaction identifier и location status, а также необязательные date-time, access technology, APN, тип PDP/PDN/PDU и location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
||||||
<li><strong>Poll Interval Negotiation</strong> (0x1C) — предлагает интервал опроса в виде Duration (единица + значение); после отправки показывается ответ UICC (принято / отклонено / изменено + длительность), а значение <em>изменено</em> становится интервалом фонового опроса (TS 102 223 §7.5.22). Форма появляется, когда карта подписана на это событие.</li>
|
<li><strong>Poll Interval Negotiation</strong> (0x1C) — предлагает интервал опроса в виде Duration (единица + значение); после отправки показывается ответ UICC (принято / отклонено / изменено + длительность), а значение <em>изменено</em> становится интервалом фонового опроса (TS 102 223 §7.5.22). Форма появляется, когда карта подписана на это событие.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-sm mb-3">Отправка события использует <code class="font-mono text-sm">ENVELOPE(Event Download)</code> по TS 102 223 / TS 131 111.</p>
|
<p class="text-sm mb-3">Отправка события использует <code class="font-mono text-sm">ENVELOPE(Event Download)</code> по TS 102 223 / TS 131 111.</p>
|
||||||
|
|||||||
+1
-1
@@ -503,7 +503,7 @@
|
|||||||
<li><strong>Location Status</strong> — dropdown: Normal / Limited / No service (tag <code class="font-mono text-sm">9B</code>)</li>
|
<li><strong>Location Status</strong> — dropdown: Normal / Limited / No service (tag <code class="font-mono text-sm">9B</code>)</li>
|
||||||
<li><strong>Access Technology Change</strong> — 13 RAT types (tag <code class="font-mono text-sm">BF</code>)</li>
|
<li><strong>Access Technology Change</strong> — 13 RAT types (tag <code class="font-mono text-sm">BF</code>)</li>
|
||||||
<li><strong>Network Rejection</strong> — full adaptive form: registration type (LU / GPRS / EPS / 5GS), location fields (MCC, MNC, LAC, RAC, TAC), access technology, and a 53-cause unified rejection cause dropdown covering EMM, GMM, 5GMM and LU causes</li>
|
<li><strong>Network Rejection</strong> — full adaptive form: registration type (LU / GPRS / EPS / 5GS), location fields (MCC, MNC, LAC, RAC, TAC), access technology, and a 53-cause unified rejection cause dropdown covering EMM, GMM, 5GMM and LU causes</li>
|
||||||
<li><strong>Data Connection Status Change</strong> (0x1D) — status (successful / rejected / dropped-deactivated), connection type (PDP / PDN / PDU), optional (E/5G)SM cause, transaction identifier and location status, plus optional access technology, APN, PDP/PDN/PDU type and location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
<li><strong>Data Connection Status Change</strong> (0x1D) — status (successful / rejected / dropped-deactivated), connection type (PDP / PDN / PDU), optional (E/5G)SM cause, transaction identifier and location status, plus optional date-time, access technology, APN, PDP/PDN/PDU type and location information (MCC/MNC + LAC + Cell ID) — TS 131 111 §7.5.25</li>
|
||||||
<li><strong>Poll Interval Negotiation</strong> (0x1C) — proposes a poll interval as a Duration (unit + value); after sending, the UICC's answer is shown (accepted / rejected / modified + duration), and a <em>modified</em> duration becomes the background poll interval (TS 102 223 §7.5.22). The form appears when the card subscribed to this event.</li>
|
<li><strong>Poll Interval Negotiation</strong> (0x1C) — proposes a poll interval as a Duration (unit + value); after sending, the UICC's answer is shown (accepted / rejected / modified + duration), and a <em>modified</em> duration becomes the background poll interval (TS 102 223 §7.5.22). The form appears when the card subscribed to this event.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="text-sm mb-3">Sending an event uses <code class="font-mono text-sm">ENVELOPE(Event Download)</code> per TS 102 223 / TS 131 111.</p>
|
<p class="text-sm mb-3">Sending an event uses <code class="font-mono text-sm">ENVELOPE(Event Download)</code> per TS 102 223 / TS 131 111.</p>
|
||||||
|
|||||||
+34
-3
@@ -9354,6 +9354,17 @@ function ramExpandedGroups(report) {
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The "more occurrences" flag of one expanded-registry page pass (pure): a
|
||||||
|
// page that promised more data ('6310', or an accepted chained retry) is
|
||||||
|
// fulfilled by the next accepted page; it stays set when the follow-up was
|
||||||
|
// refused/failed or the page cap stopped the loop - only then is the listing
|
||||||
|
// "first page only".
|
||||||
|
function ramExpandedMore(promised, sw, page) {
|
||||||
|
if (!promised) return false;
|
||||||
|
if (page >= 8) return true;
|
||||||
|
return !ramRemoteSwOk(sw);
|
||||||
|
}
|
||||||
|
|
||||||
// Summary of the expanded-registry pass (pure): which query form the card
|
// Summary of the expanded-registry pass (pure): which query form the card
|
||||||
// accepted per listing and where the run fell back to the compact listing.
|
// accepted per listing and where the run fell back to the compact listing.
|
||||||
// Shown in the explorer so a fallback (and the missing package versions) is
|
// Shown in the explorer so a fallback (and the missing package versions) is
|
||||||
@@ -9636,7 +9647,7 @@ async function ramExplore(sp) {
|
|||||||
return res.por.decoded ? (res.por.decoded.last_response_data || '') : '';
|
return res.por.decoded ? (res.por.decoded.last_response_data || '') : '';
|
||||||
};
|
};
|
||||||
let page = 0;
|
let page = 0;
|
||||||
let more = false;
|
let promised = false;
|
||||||
while (accepted && page++ < 8) {
|
while (accepted && page++ < 8) {
|
||||||
const apdu = ramExpandedQueryApdu(p1, form, page > 1);
|
const apdu = ramExpandedQueryApdu(p1, form, page > 1);
|
||||||
const what = page > 1 ? 'P2=03 (' + form + ')' : 'P2=02 (' + form + ')';
|
const what = page > 1 ? 'P2=03 (' + form + ')' : 'P2=02 (' + form + ')';
|
||||||
@@ -9660,8 +9671,11 @@ async function ramExplore(sp) {
|
|||||||
// 6310 signal with the GET RESPONSE's own SW); a refused retry
|
// 6310 signal with the GET RESPONSE's own SW); a refused retry
|
||||||
// must not cost another packet (v3.7.0)
|
// must not cost another packet (v3.7.0)
|
||||||
if (sw !== '6310' && !(pending && ramRemoteSwOk(sw))) break;
|
if (sw !== '6310' && !(pending && ramRemoteSwOk(sw))) break;
|
||||||
more = true;
|
promised = true;
|
||||||
}
|
}
|
||||||
|
// An unfulfilled 6310 only: a follow-up page that was accepted and
|
||||||
|
// ended the listing fulfils the promise.
|
||||||
|
const more = ramExpandedMore(promised, sw, page);
|
||||||
const parsed = data ? parser(data) : [];
|
const parsed = data ? parser(data) : [];
|
||||||
if (parsed.length) {
|
if (parsed.length) {
|
||||||
expandedForm = form;
|
expandedForm = form;
|
||||||
@@ -11761,7 +11775,7 @@ function cardsRender() {
|
|||||||
const tbody = document.getElementById('cards-tbody');
|
const tbody = document.getElementById('cards-tbody');
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
if (!cards.length) {
|
if (!cards.length) {
|
||||||
tbody.innerHTML = '<tr><td colspan="10" class="py-2 text-gray-400">' + t('No cards defined.') + '</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="8" class="py-2 text-gray-400">' + t('No cards defined.') + '</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The preset of the equipped card (normalised ICCID match): its row is
|
// The preset of the equipped card (normalised ICCID match): its row is
|
||||||
@@ -13021,6 +13035,20 @@ const REJECTION_CAUSES = [
|
|||||||
|
|
||||||
// Access Technology coding (TS 102 223 V18.3.0 8.61, tags '3F'/'BF'): the
|
// Access Technology coding (TS 102 223 V18.3.0 8.61, tags '3F'/'BF'): the
|
||||||
// single/multiple technology objects and the PLI qualifier share this list.
|
// single/multiple technology objects and the PLI qualifier share this list.
|
||||||
|
// Date-Time and Time zone object (TS 102 223 8.39): seven semi-octet BCD
|
||||||
|
// bytes in the TS 23.040 9.2.3.11 order (units in the high nibble) - YY MM
|
||||||
|
// DD hh mm ss plus the time zone in quarters of an hour, its sign in bit 3
|
||||||
|
// of the first semi-octet (1 = west of GMT).
|
||||||
|
function eventDateTimeTlv(d) {
|
||||||
|
const semi = v => ((v % 10) << 4) | Math.floor(v / 10);
|
||||||
|
const hx = b => b.toString(16).padStart(2, '0');
|
||||||
|
const offMin = -d.getTimezoneOffset(); // minutes east of UTC
|
||||||
|
const tz = semi(Math.round(Math.abs(offMin) / 15)) | (offMin < 0 ? 0x08 : 0);
|
||||||
|
return '2607' + hx(semi(d.getFullYear() % 100)) + hx(semi(d.getMonth() + 1)) +
|
||||||
|
hx(semi(d.getDate())) + hx(semi(d.getHours())) + hx(semi(d.getMinutes())) +
|
||||||
|
hx(semi(d.getSeconds())) + hx(tz);
|
||||||
|
}
|
||||||
|
|
||||||
const ACCESSTECH = [
|
const ACCESSTECH = [
|
||||||
{v:0,l:'GSM'},{v:1,l:'TIA/EIA-553-A'},{v:2,l:'TIA/EIA-136-270'},{v:3,l:'UTRAN'},
|
{v:0,l:'GSM'},{v:1,l:'TIA/EIA-553-A'},{v:2,l:'TIA/EIA-136-270'},{v:3,l:'UTRAN'},
|
||||||
{v:4,l:'TETRA'},{v:5,l:'TIA/EIA-95-B'},{v:6,l:'CDMA2000 1x'},{v:7,l:'CDMA2000 HRPD'},
|
{v:4,l:'TETRA'},{v:5,l:'TIA/EIA-95-B'},{v:6,l:'CDMA2000 1x'},{v:7,l:'CDMA2000 HRPD'},
|
||||||
@@ -13165,6 +13193,8 @@ const EVENT_FORMS = {
|
|||||||
{v:2,l:'02 \u2014 PDU connection'}] },
|
{v:2,l:'02 \u2014 PDU connection'}] },
|
||||||
{ id: 'cause', label: '(E/5G)SM cause (optional)', type: 'number', min: '0', max: '255', placeholder: '0\u2013255, e.g. 26' },
|
{ id: 'cause', label: '(E/5G)SM cause (optional)', type: 'number', min: '0', max: '255', placeholder: '0\u2013255, e.g. 26' },
|
||||||
{ id: 'ti', label: 'Transaction identifier', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: 'hex byte, e.g. 00' },
|
{ id: 'ti', label: 'Transaction identifier', type: 'text', pattern: '[0-9A-Fa-f]{2}', placeholder: 'hex byte, e.g. 00' },
|
||||||
|
{ id: 'datetime', label: 'Date-Time and Time zone (optional)', type: 'select',
|
||||||
|
opts: [{v:'',l:'\u2014 not present \u2014'},{v:'now',l:'current date-time (host clock)'}] },
|
||||||
{ id: 'mcc', label: 'MCC (Location Information, optional)', type: 'text', pattern: '\\d{3}', placeholder: 'e.g. 250' },
|
{ id: 'mcc', label: 'MCC (Location Information, optional)', type: 'text', pattern: '\\d{3}', placeholder: 'e.g. 250' },
|
||||||
{ id: 'mnc', label: 'MNC', type: 'text', pattern: '\\d{2,3}', placeholder: 'e.g. 01' },
|
{ id: 'mnc', label: 'MNC', type: 'text', pattern: '\\d{2,3}', placeholder: 'e.g. 01' },
|
||||||
{ id: 'lac', label: 'LAC', type: 'text', pattern: '[0-9A-Fa-f]{4}', placeholder: 'hex, e.g. 00FF' },
|
{ id: 'lac', label: 'LAC', type: 'text', pattern: '[0-9A-Fa-f]{4}', placeholder: 'hex, e.g. 00FF' },
|
||||||
@@ -13189,6 +13219,7 @@ const EVENT_FORMS = {
|
|||||||
if (v.cause !== '' && v.cause !== undefined)
|
if (v.cause !== '' && v.cause !== undefined)
|
||||||
d += 'AE01' + ((parseInt(v.cause) || 0) & 0xFF).toString(16).padStart(2, '0');
|
d += 'AE01' + ((parseInt(v.cause) || 0) & 0xFF).toString(16).padStart(2, '0');
|
||||||
d += '1C01' + (v.ti || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2);
|
d += '1C01' + (v.ti || '00').replace(/[^0-9a-fA-F]/g, '').padStart(2, '0').slice(0, 2);
|
||||||
|
if (v.datetime === 'now') d += eventDateTimeTlv(new Date());
|
||||||
if (v.mcc)
|
if (v.mcc)
|
||||||
d += '1307' + encPlmn(v.mcc, v.mnc || '01') +
|
d += '1307' + encPlmn(v.mcc, v.mnc || '01') +
|
||||||
(v.lac || '0000').toUpperCase() + (v.cell || '0001').toUpperCase();
|
(v.lac || '0000').toUpperCase() + (v.cell || '0001').toUpperCase();
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'simple-v346';
|
const CACHE = 'simple-v347';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -124,3 +124,28 @@ test('the access technology options follow the pinned 8.61 coding', () => {
|
|||||||
assert.strictEqual(opts[10].l, 'NG-RAN');
|
assert.strictEqual(opts[10].l, 'NG-RAN');
|
||||||
assert.strictEqual(opts[12].l, 'Satellite E-UTRAN');
|
assert.strictEqual(opts[12].l, 'Satellite E-UTRAN');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('eventDateTimeTlv builds the 8.39 date-time object', () => {
|
||||||
|
const d = o => ({
|
||||||
|
getFullYear: () => o.year, getMonth: () => o.month - 1, getDate: () => o.day,
|
||||||
|
getHours: () => o.hour, getMinutes: () => o.min, getSeconds: () => o.sec,
|
||||||
|
getTimezoneOffset: () => o.tzMin,
|
||||||
|
});
|
||||||
|
// UTC+3, 2026-09-30 21:55:00 -> swapped BCD + 12 quarters
|
||||||
|
assert.strictEqual(
|
||||||
|
eventDateTimeTlv(d({ year: 2026, month: 9, day: 30, hour: 21, min: 55, sec: 0, tzMin: -180 })),
|
||||||
|
'260762900312550021');
|
||||||
|
// UTC-2:30 -> 10 quarters with the sign bit (bit 3 of the first semi-octet)
|
||||||
|
assert.strictEqual(
|
||||||
|
eventDateTimeTlv(d({ year: 2026, month: 1, day: 5, hour: 8, min: 7, sec: 9, tzMin: 150 })),
|
||||||
|
'260762105080709009');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the data connection event can carry the current date-time', () => {
|
||||||
|
const build = EVENT_FORMS[0x1D].build;
|
||||||
|
assert.match(build({ status: '0', type: '0', ti: '00', loc_status: '0', datetime: 'now' }),
|
||||||
|
/^9D0100AA01001C01002607[0-9A-F]{14}9B0100$/);
|
||||||
|
// left out by default
|
||||||
|
assert.strictEqual(build({ status: '0', type: '0', ti: '00', loc_status: '0' }),
|
||||||
|
'9D0100AA01001C01009B0100');
|
||||||
|
});
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function extractFunc(src, name) {
|
|||||||
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtVersion', 'ramMenuState', 'ramEntryKind', 'ramMenuHtml', 'ramRawTlvsHtml', 'ramNvLine', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramMergeExpanded', 'ramStepLine', 'ramStepComponents', 'ramInstallFailHint', 'ramProbeParse', 'ramCompatVerdict', 'ramCompatProbeNote', 'ramCompatImportRows', 'ramCompatRowsHtml', 'ramCompatFailureHtml', 'ramCompatDetailHtml', 'lookupSw', 'ramExpandedDetailsInit', 'ramExpandedDetailsChanged', 'ramGetStatusApdu', 'ramDeleteApdu',
|
const FNS = ['berLenStr', 'buildApdu', 'escHtml', 'esc', 'chainInit', 'chainRamBuildRowHex', 'ramFmtLifecycle', 'ramFmtVersion', 'ramMenuState', 'ramEntryKind', 'ramMenuHtml', 'ramRawTlvsHtml', 'ramNvLine', 'ramFmtPrivileges', 'ramRenderExploreHtml', 'ramMergeExpanded', 'ramStepLine', 'ramStepComponents', 'ramInstallFailHint', 'ramProbeParse', 'ramCompatVerdict', 'ramCompatProbeNote', 'ramCompatImportRows', 'ramCompatRowsHtml', 'ramCompatFailureHtml', 'ramCompatDetailHtml', 'lookupSw', 'ramExpandedDetailsInit', 'ramExpandedDetailsChanged', 'ramGetStatusApdu', 'ramDeleteApdu',
|
||||||
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
|
'stkParamsBuild', 'ramRemoteSwOk', 'spPorAccepted', 'ramIncrementCntr', 'ramDeleteFromExplorer', 'ramListingSpi2', 'ramRemoveFromExplorer', 'ramHasInstance', 'ramExpandedQueryApdu',
|
||||||
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'ramParseModuleAids', 'parseTLV', '_parseE3Entry', '_parseMenuEntries',
|
'_parseRawElfEntry', '_parseRawAppEntry', 'ramParseElfStatus', 'ramParseAppStatus', 'ramParseModuleAids', 'parseTLV', '_parseE3Entry', '_parseMenuEntries',
|
||||||
'ramExpandedReport', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
|
'ramExpandedReport', 'ramExpandedMore', 'ramExpandedTags', 'ramExpandedGroups', 'ramExpandedElfForm', 'ramElfVersionHint', 'ramChainGetResponse', 'ramDeriveElfVersions', 'ramElfAppletCandidate',
|
||||||
'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramShowCntrLow',
|
'spCntrLow', 'ramCntrLowHtml', 'ramCntrLowPresetIdx', 'ramShowCntrLow',
|
||||||
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
'spKeysetKvnOf', 'spKeysetList', 'spKeysetFor', 'spKeysetCheck',
|
||||||
'spKeysetOptionsHtml', 'spPresetIdx', 'tarPresetIdx',
|
'spKeysetOptionsHtml', 'spPresetIdx', 'tarPresetIdx',
|
||||||
@@ -1489,3 +1489,16 @@ test('SIM toolkit mode hides the UICC file-access row', () => {
|
|||||||
assert.ok(src.includes('rc-tk-access-row'),
|
assert.ok(src.includes('rc-tk-access-row'),
|
||||||
'the access row must follow the CA/EA visibility');
|
'the access row must follow the CA/EA visibility');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ramExpandedMore marks only an unfulfilled 6310', () => {
|
||||||
|
// no promise -> nothing to report
|
||||||
|
assert.strictEqual(ramExpandedMore(false, '9000', 1), false);
|
||||||
|
// the follow-up page was accepted and ended the listing
|
||||||
|
assert.strictEqual(ramExpandedMore(true, '9000', 2), false);
|
||||||
|
// the follow-up page was refused
|
||||||
|
assert.strictEqual(ramExpandedMore(true, '6A86', 2), true);
|
||||||
|
// the page cap stopped the paging
|
||||||
|
assert.strictEqual(ramExpandedMore(true, '6310', 8), true);
|
||||||
|
// a transport failure (no remote status word) leaves it unfulfilled
|
||||||
|
assert.strictEqual(ramExpandedMore(true, 'index out of range', 2), true);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user