// Author: cirakg & claude ai // Summary: Tests for the Add code form: a valid and invalid payload for every symbology, options, notes, size warnings and the cache. import Toybox.Lang; import Toybox.Test; (:test) module CodeFormCases { // [symbology, payload, part of the status] for contents every // symbology accepts (all fake). const VALID = [ [Codes.QR, "https://example.com/card/0042", ""], [Codes.CODE128, "Member-0042", ""], [Codes.CODE39, "CARD 0042", ""], [Codes.EAN13, " 590123412345 ", "check digit 7 added"], [Codes.EAN8, "96385074", ""], [Codes.UPCA, "03600029145", "check digit 2 added"], [Codes.UPCE, "0123456", ""], [Codes.ITF, "123456789", ""], // with its check digit: 10 digits [Codes.CODABAR, "40156", "start and stop characters A...A added"], [Codes.DATAMATRIX, "PARCEL-FAKE-0042", ""], [Codes.PDF417, "Boarding pass FAKE 0042", ""], [Codes.AZTEC, "RAIL-FAKE-0042", ""] ]; // [symbology, payload, check-digit mode, expected status part]. function invalid() as Array { return [ [Codes.EAN13, "5901234123450", Codes.CHECK_ADD_VERIFY, "Not added: EAN-13: check digit should be 7, got 0. Choose 'Fix if wrong' to correct it"], [Codes.EAN13, "59012341234", Codes.CHECK_ADD_VERIFY, "EAN-13 needs 12 or 13 digits, got 11"], [Codes.EAN13, "590123412345", Codes.CHECK_OFF, "EAN-13 with the check digit off needs all 13 digits"], [Codes.EAN8, "12A4567", Codes.CHECK_ADD_VERIFY, "EAN-8 takes digits only"], [Codes.UPCA, "123", Codes.CHECK_ADD_VERIFY, "UPC-A needs 11 or 12 digits, got 3"], [Codes.UPCE, "2123456", Codes.CHECK_ADD_VERIFY, "UPC-E: the number system must be 0 or 1, got 2"], [Codes.CODE128, "Café", Codes.CHECK_ADD_VERIFY, "Code 128: 'é' (position 4) is not a printable ASCII character"], [Codes.CODE39, "card", Codes.CHECK_ADD_VERIFY, "Code 39: 'c' (position 1)"], [Codes.ITF, "123", Codes.CHECK_OFF, "ITF needs an even number of digits: add a leading 0"], [Codes.CODABAR, "A12X3B", Codes.CHECK_ADD_VERIFY, "Codabar: 'X' isn't allowed there"], [Codes.QR, SettingsFixtures.repeat("a", 2400), Codes.CHECK_ADD_VERIFY, "QR: too long for a QR code"], [Codes.DATAMATRIX, SettingsFixtures.repeat("a", 1600), Codes.CHECK_ADD_VERIFY, "too long for Data Matrix"], [Codes.PDF417, SettingsFixtures.repeat("a1", 1500), Codes.CHECK_ADD_VERIFY, "PDF417: too"], [Codes.AZTEC, SettingsFixtures.repeat("é", 2000), Codes.CHECK_ADD_VERIFY, "Aztec: too long"] ] as Array; } } // Every symbology accepts a typical payload; 2D codes are cached encoded. (:test) function settingsAddsEverySymbology(logger as Test.Logger) as Boolean { var vault = SettingsFixtures.vault(); var s = SettingsFixtures.store(); var ok = true; for (var i = 0; i < CodeFormCases.VALID.size(); i++) { var c = CodeFormCases.VALID[i] as Array; var symbology = c[0] as Number; SettingsFixtures.code(s, symbology, "Card " + i, c[1] as String); SettingsFixtures.run(vault, s); var status = SettingsFixtures.text(s, CodeForm.STATUS); var ids = vault.ids(); if (ids.size() != i + 1 || status.find("Added " + ids[i] + " Card " + i) != 0 || status.find(c[2] as String) == null) { logger.error(Codes.name(symbology) + ": " + status); ok = false; continue; } var cached = vault.loadCodeCache(ids[i]); if (Codes.isMatrix(symbology) != (cached != null && Codes.Matrix.fromBytes(cached) != null)) { logger.error(Codes.name(symbology) + ": cache " + (cached != null)); ok = false; } if (!SettingsFixtures.text(s, CodeForm.PAYLOAD).equals("") || Forms.flag(s, CodeForm.SWITCH)) { logger.error(Codes.name(symbology) + ": form not cleared"); ok = false; } } // Linear contents are stored trimmed. return ok && SettingsFixtures.secretOf(vault, vault.ids()[3]).equals("590123412345"); } // Every symbology's typical mistakes give the encoder's message; nothing // is added and only the contents are cleared. (:test) function settingsRejectsInvalidCodes(logger as Test.Logger) as Boolean { var vault = SettingsFixtures.vault(); var cases = CodeFormCases.invalid(); var ok = true; for (var i = 0; i < cases.size(); i++) { var c = cases[i]; var s = SettingsFixtures.store(); SettingsFixtures.code(s, c[0] as Number, "Card", c[1] as String); s.set("codeCheckDigit", c[2] as Number); SettingsFixtures.run(vault, s); var status = SettingsFixtures.text(s, CodeForm.STATUS); if (status.find(c[3] as String) == null || vault.ids().size() != 0 || !SettingsFixtures.text(s, "codeName").equals("Card") || !SettingsFixtures.text(s, CodeForm.PAYLOAD).equals("")) { logger.error("case " + i + " (" + Codes.name(c[0] as Number) + "): " + status); ok = false; } } return ok; } (:test) function settingsChecksCodeFields(logger as Test.Logger) as Boolean { var vault = SettingsFixtures.vault(); var s = SettingsFixtures.store(); SettingsFixtures.code(s, Codes.QR, " ", "x"); SettingsFixtures.run(vault, s); var ok = SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Name: enter a name for the card"); SettingsFixtures.code(s, Codes.QR, "Card", " "); SettingsFixtures.run(vault, s); ok = ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Contents: missing"); SettingsFixtures.code(s, 12, "Card", "x"); SettingsFixtures.run(vault, s); ok = ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Type: choose one of the 12 barcode types"); SettingsFixtures.code(s, Codes.QR, "Card", SettingsFixtures.repeat("é", 2049)); SettingsFixtures.run(vault, s); return ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Contents: too long (4098 bytes, at most 4096)") && vault.ids().size() == 0; } // The form's options end up in Entry.options; "Fix if wrong" corrects the // check digit and says so. (:test) function settingsStoresCodeOptions(logger as Test.Logger) as Boolean { var vault = SettingsFixtures.vault(); var s = SettingsFixtures.store(); SettingsFixtures.code(s, Codes.EAN13, "Gym", "5901234123450"); s.set("codeCheckDigit", Codes.CHECK_FIX); s.set("codeShowText", false); s.set("codeEcLevel", 3); s.set("codeGroup", "Sport"); s.set("codeLocked", false); SettingsFixtures.run(vault, s); if (vault.ids().size() != 1) { logger.error(SettingsFixtures.text(s, CodeForm.STATUS)); return false; } var e = vault.open(vault.ids()[0]); var ok = e.options == (3 | (Codes.CHECK_FIX << Codes.OPT_CHECK_SHIFT)) && e.group.equals("Sport") && e.type == Codes.EAN13; e.wipeSecrets(); return ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "check digit fixed: 7 (was 0)"); } // Codes that encode but can't be shown at the minimum module size on this // screen are added with the README 12.2 warning. (:test) function settingsWarnsWhenACodeIsTooLarge(logger as Test.Logger) as Boolean { var vault = SettingsFixtures.vault(); var s = SettingsFixtures.store(); // About version 39 at level L: over 360 px at 2 px per module. SettingsFixtures.code(s, Codes.QR, "Big", SettingsFixtures.repeat("Ab", 1400)); s.set("codeEcLevel", 0); SettingsFixtures.run(vault, s); var qr = SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Too large for this screen: needs 2 px per module"); SettingsFixtures.code(s, Codes.CODE128, "Wide", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn"); SettingsFixtures.run(vault, s); return qr && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Too large for this screen: needs") && vault.ids().size() == 2; } // A locked card sent while locked waits as pending; no cache is written // for it (its key isn't available). (:test) function settingsLockedCodeWaitsForThePin(logger as Test.Logger) as Boolean { var vault = SettingsFixtures.vault(); StorageFixtures.setPin(vault, StorageFixtures.PIN); vault.lock(); var s = SettingsFixtures.store(); SettingsFixtures.code(s, Codes.DATAMATRIX, "Parcel", "PARCEL-FAKE-0042"); s.set("codeLocked", true); SettingsFixtures.run(vault, s); var id = vault.pendingIds().size() == 1 ? vault.pendingIds()[0] : ""; return id.length() > 0 && vault.storage().getValue("x." + id) == null && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "It waits for your PIN on the watch"); }