test: remove standalone serial and barcode tests

This commit is contained in:
Mihir Kandoi
2026-09-08 12:54:32 +05:30
parent 687c7d55ba
commit f80cac927d
2 changed files with 0 additions and 432 deletions

View File

@@ -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);
});

View File

@@ -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("<", "&lt;").replaceAll(">", "&gt;"),
},
},
};
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(
"<serial>",
field,
{ for_print: true },
{ serial_no: "internal-id" }
),
"&lt;serial&gt;"
);
});