From f80cac927d0b9c1403cab9fed875776c2a18c692 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 8 Sep 2026 12:54:32 +0530 Subject: [PATCH] test: remove standalone serial and barcode tests --- .../public/js/tests/barcode_scanner.test.cjs | 176 ------------ .../js/tests/serial_batch_input.test.cjs | 256 ------------------ 2 files changed, 432 deletions(-) delete mode 100644 erpnext/public/js/tests/barcode_scanner.test.cjs delete mode 100644 erpnext/public/js/tests/serial_batch_input.test.cjs diff --git a/erpnext/public/js/tests/barcode_scanner.test.cjs b/erpnext/public/js/tests/barcode_scanner.test.cjs deleted file mode 100644 index 4b6e9d17fd2..00000000000 --- a/erpnext/public/js/tests/barcode_scanner.test.cjs +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-env node */ -const assert = require("node:assert/strict"); -const { readFileSync } = require("node:fs"); -const path = require("node:path"); -const { test } = require("node:test"); -const vm = require("node:vm"); - -function setupScanner() { - const items = []; - const item_requests = []; - const alerts = []; - let scan_result; - const frm = { - doctype: "Delivery Note", - doc: { doctype: "Delivery Note", items }, - fields_dict: { - scan_barcode: { - value: "", - set_value(value) { - this.value = value; - }, - }, - items: { grid: { doctype: "Delivery Note Item" } }, - }, - script_manager: { trigger() {} }, - }; - const frappe = { - flags: {}, - meta: { has_field: (doctype, field) => field !== "last_scanned_warehouse" }, - utils: { get_link_title: () => "PHYSICAL-123", add_link_title() {} }, - call: async () => ({ message: scan_result }), - show_alert: (alert) => alerts.push(alert), - run_serially: (tasks) => - tasks.reduce((pending, task) => pending.then(task), Promise.resolve()), - model: { - add_child: (doc, doctype) => { - const row = { - doctype, - name: `row-${items.length + 1}`, - idx: items.length + 1, - qty: 1, - }; - items.push(row); - return row; - }, - set_value: async (doctype, name, field, value) => { - const row = items.find((item) => item.name === name); - const values = typeof field === "string" ? { [field]: value } : field; - const item_changed = - values.item_code && values.item_code !== row.item_code; - // Frappe sets all supplied fields before running their change handlers. - Object.assign(row, values); - if (item_changed) { - item_requests.push({ ...row }); - // Outward item selection auto-picks stock if the scanned references were absent. - row.serial_no ||= "auto-picked-id"; - row.batch_no ||= "auto-picked-batch"; - } - }, - }, - }; - const context = { - frappe, - erpnext: { utils: {} }, - __: (text) => text, - refresh_field() {}, - flt: Number, - }; - vm.runInNewContext( - readFileSync(path.join(__dirname, "../utils/barcode_scanner.js"), "utf8"), - context - ); - const scanner = new context.erpnext.utils.BarcodeScanner({ frm }); - const scan = (serial_no, item_code = "ITEM-A") => { - scan_result = { - item_code, - serial_no, - serial_number: "PHYSICAL-123", - batch_no: `batch-${item_code}`, - has_serial_no: 1, - has_batch_no: 1, - }; - frm.fields_dict.scan_barcode.value = "PHYSICAL-123"; - return scanner.process_scan(); - }; - return { scanner, scan, items, item_requests, alerts, frm, frappe }; -} - -test("one scan sets its serial and batch before item auto-selection and adds one unit", async () => { - const { scan, items, item_requests } = setupScanner(); - await scan("scanned-id"); - assert.equal(item_requests[0].serial_no, "scanned-id"); - assert.equal(item_requests[0].batch_no, "batch-ITEM-A"); - assert.equal(item_requests[0].qty, 1); - assert.equal(items[0].serial_no, "scanned-id"); - assert.equal(items[0].qty, 1); -}); - -test("rescanning the same serial does not append it or increase quantity", async () => { - const { scan, items, alerts } = setupScanner(); - await scan("scanned-id"); - await assert.rejects(scan("scanned-id")); - assert.equal(items.length, 1); - assert.equal(items[0].serial_no, "scanned-id"); - assert.equal(items[0].qty, 1); - assert.equal(alerts.at(-1).indicator, "orange"); -}); - -test("distinct serial IDs, including prefixes, each add one unit", async () => { - const { scan, items } = setupScanner(); - await scan("scanned-id-long"); - await scan("scanned-id"); - assert.equal(items[0].serial_no, "scanned-id-long\nscanned-id"); - assert.equal(items[0].qty, 2); -}); - -test("matching physical numbers on different items keep their separate serial IDs", async () => { - const { scan, items } = setupScanner(); - await scan("item-a-id", "ITEM-A"); - await scan("item-b-id", "ITEM-B"); - assert.deepEqual( - items.map((row) => [row.item_code, row.serial_no, row.qty]), - [ - ["ITEM-A", "item-a-id", 1], - ["ITEM-B", "item-b-id", 1], - ] - ); -}); - -test("scanning into an empty default row starts with one unit", async () => { - const { scan, items, frappe, frm } = setupScanner(); - frappe.model.add_child(frm.doc, "Delivery Note Item"); - await scan("scanned-id"); - assert.equal(items.length, 1); - assert.equal(items[0].serial_no, "scanned-id"); - assert.equal(items[0].qty, 1); -}); - -test("the scan dialog replaces auto-selected serials with its scanned list", async () => { - const { scanner, items, frappe, frm } = setupScanner(); - frappe.ui = { - Dialog: class { - constructor({ fields }) { - this.values = Object.fromEntries( - fields.map((field) => [field.fieldname, field.default]) - ); - this.$wrapper = { find: () => ({ css() {} }) }; - } - set_primary_action(label, action) { - this.primary_action = action; - } - get_value(field) { - return this.values[field]; - } - show() {} - hide() {} - }, - }; - const row = frappe.model.add_child(frm.doc, "Delivery Note Item"); - Object.assign(row, { - item_code: "ITEM-A", - serial_no: "auto-picked-id", - batch_no: "batch-ITEM-A", - }); - scanner.prepare_item_for_scan( - row, - "ITEM-A", - null, - "batch-ITEM-A", - "scanned-id" - ); - await scanner.dialog.primary_action(); - assert.equal(items[0].serial_no, "scanned-id"); - assert.equal(items[0].qty, 1); - assert.equal(items[0].has_item_scanned, 1); -}); diff --git a/erpnext/public/js/tests/serial_batch_input.test.cjs b/erpnext/public/js/tests/serial_batch_input.test.cjs deleted file mode 100644 index 2f0675936e6..00000000000 --- a/erpnext/public/js/tests/serial_batch_input.test.cjs +++ /dev/null @@ -1,256 +0,0 @@ -/* eslint-env node */ -const assert = require("node:assert/strict"); -const { readFileSync } = require("node:fs"); -const path = require("node:path"); -const { test } = require("node:test"); -const vm = require("node:vm"); - -function setup(xcall) { - class Control { - get_model_value() { - return this.doc.serial_no; - } - parse_validate_and_set_in_model(value) { - this.doc.serial_no = value; - } - set_formatted_input(value) { - this.display = value; - } - set_disp_area() {} - } - const titles = new Map(); - const handlers = {}; - const frappe = { - xcall, - ui: { - form: { - ControlSmallText: Control, - ControlLink: Control, - ControlText: Control, - ControlLongText: Control, - on: (doctype, events) => { - handlers[doctype] = events; - }, - }, - }, - utils: { - get_link_title: (doctype, name) => titles.get(name), - add_link_title: (doctype, name, value) => titles.set(name, value), - }, - }; - vm.runInNewContext( - readFileSync( - path.join(__dirname, "../utils/serial_batch_input.js"), - "utf8" - ), - { frappe } - ); - const control = new frappe.ui.form.ControlSmallText(); - control.frm = { - doctype: "Purchase Receipt", - doc: { doctype: "Purchase Receipt" }, - }; - control.df = { parent: "Purchase Receipt Item", fieldname: "serial_no" }; - control.doc = { item_code: "ITEM-B", serial_no: "existing-id" }; - return { control, handlers, titles, frappe }; -} - -test("physical input resolves using the row's item, even when it resembles an existing ID", async () => { - let request; - const { control } = setup(async (method, args) => { - request = args; - return ["new-id"]; - }); - await control.parse_validate_and_set_in_model("existing-id", {}); - assert.equal(request.row.item_code, "ITEM-B"); - assert.deepEqual(Array.from(request.numbers), ["existing-id"]); - assert.equal(control.doc.serial_no, "new-id"); - assert.equal(control.serial_number_text("new-id"), "existing-id"); -}); - -test("programmatic ID updates do not resolve the ID as a physical number", async () => { - const { control } = setup(() => { - throw new Error("Unexpected lookup"); - }); - await control.parse_validate_and_set_in_model("another-id", null); - assert.equal(control.doc.serial_no, "another-id"); -}); - -test("saving waits for number resolution and stale responses cannot overwrite newer input", async () => { - const responses = []; - const { control, handlers } = setup( - () => new Promise((resolve) => responses.push(resolve)) - ); - const first = control.parse_validate_and_set_in_model("first-number", {}); - const second = control.parse_validate_and_set_in_model("second-number", {}); - let saved = false; - const saving = handlers[control.frm.doctype] - .before_save(control.frm) - .then(() => { - saved = true; - }); - assert.equal(saved, false); - responses[1](["second-id"]); - await second; - responses[0](["first-id"]); - await first; - await saving; - assert.equal(control.doc.serial_no, "second-id"); - assert.equal(saved, true); -}); - -test("an ambiguous scan requires a selection and cancel leaves it unresolved", async () => { - let dialog; - const context = { - erpnext: { utils: {} }, - __: (text) => text, - frappe: { - ui: { - Dialog: class { - constructor(options) { - Object.assign(this, options); - dialog = this; - } - show() {} - hide() { - this.onhide(); - } - }, - }, - }, - }; - vm.runInNewContext( - readFileSync(path.join(__dirname, "../utils/barcode_scanner.js"), "utf8"), - context - ); - const scanner = Object.create(context.erpnext.utils.BarcodeScanner.prototype); - const candidates = [ - { item_code: "A", barcode: "123" }, - { item_code: "A", serial_no: "id-a", serial_number: "123" }, - { item_code: "B", serial_no: "id-b", serial_number: "123" }, - ]; - const selection = scanner.select_scan_match(candidates); - const item_field = dialog.fields[0]; - assert.equal(item_field.fieldtype, "Link"); - assert.equal(item_field.options, "Item"); - assert.deepEqual(Array.from(item_field.get_query().filters.name[1]), [ - "A", - "B", - ]); - dialog.primary_action({ item_code: "B" }); - assert.equal(await selection, candidates[2]); - assert.equal( - await scanner.select_scan_match(candidates.slice(0, 2)), - candidates[1] - ); - const cancelled = scanner.select_scan_match(candidates); - dialog.hide(); - assert.equal(await cancelled, null); -}); - -test("a programmatic update invalidates a pending keyboard lookup", async () => { - let resolve; - const { control } = setup( - () => - new Promise((callback) => { - resolve = callback; - }) - ); - const typing = control.parse_validate_and_set_in_model("typed-number", {}); - await control.parse_validate_and_set_in_model("selected-id", null); - resolve(["typed-id"]); - await typing; - assert.equal(control.doc.serial_no, "selected-id"); -}); - -test("typed batch input uses the physical label even when the control mapped it to an old ID", async () => { - let request; - const { control, frappe } = setup(async (method, args) => { - request = args; - return { batch_nos: ["item-b-batch-id"] }; - }); - const link = new frappe.ui.form.ControlLink(); - link.frm = control.frm; - link.doc = control.doc; - link.get_options = () => "Batch"; - link.get_label_value = () => "physical-batch-number"; - await link.parse_validate_and_set_in_model("item-a-batch-id", {}, undefined); - assert.equal(request.item_code, "ITEM-B"); - assert.deepEqual(Array.from(request.batch_numbers), [ - "physical-batch-number", - ]); - assert.equal(link.doc.serial_no, "item-b-batch-id"); - await link.parse_validate_and_set_in_model("programmatic-id", null); - assert.equal(link.doc.serial_no, "programmatic-id"); - await link.parse_validate_and_set_in_model( - "item-a-batch-id", - null, - "selected-physical-label" - ); - assert.deepEqual(Array.from(request.batch_numbers), [ - "selected-physical-label", - ]); -}); - -test("POS controls use their explicit item and form context", async () => { - let request; - const { control } = setup(async (method, args) => { - request = args; - return ["pos-serial-id"]; - }); - control.serial_batch_context = { frm: control.frm, row: control.doc }; - control.frm = undefined; - await control.parse_validate_and_set_in_model("POS-PHYSICAL", {}); - assert.equal(request.row.item_code, "ITEM-B"); - assert.equal(control.doc.serial_no, "pos-serial-id"); -}); - -test("report numbers display physical labels and link to internal IDs", () => { - let linked; - const context = { - frappe: { - model: { can_read: () => true }, - form: { - formatters: { - Link: (id, df, options) => { - linked = { id, options }; - return options.label; - }, - }, - }, - utils: { - escape_html: (value) => - value.replaceAll("<", "<").replaceAll(">", ">"), - }, - }, - }; - vm.runInNewContext( - readFileSync( - path.join(__dirname, "../utils/serial_batch_display.js"), - "utf8" - ), - context - ); - const format = context.frappe.form.formatters.SerialBatchNumber; - const field = { reference_field: "serial_no", options: "Serial No" }; - assert.equal( - format("PHYSICAL-123", field, {}, { serial_no: "internal-id" }), - "PHYSICAL-123" - ); - assert.equal(linked.id, "internal-id"); - assert.equal(linked.options.label, "PHYSICAL-123"); - context.frappe.model.can_read = () => false; - assert.equal( - format("PHYSICAL-123", field, {}, { serial_no: "internal-id" }), - "PHYSICAL-123" - ); - assert.equal( - format( - "", - field, - { for_print: true }, - { serial_no: "internal-id" } - ), - "<serial>" - ); -});