suitescript-mocks
Version:
Set of mocks for unit testing Netsuite Suitescript 2.*
525 lines (465 loc) • 14.9 kB
JavaScript
const datefns = require("date-fns");
const structuredClone = require("core-js-pure/actual/structured-clone");
const { randomUUID } = require("node:crypto");
const SuiteScriptMocks = require("../../index.cjs");
const Field = require("./Field.cjs");
const Sublist = require("./Sublist.cjs");
const {
options,
required,
addPromise,
dynamicModeOnly,
standardModeOnly,
assignConstructor,
} = require("../../helpers.cjs");
class Record {
id = null;
type = null;
fields = {};
sublists = {};
subrecords = {};
isDynamic = false;
version = 1;
initialize = () => {
this.fields = structuredClone(this.fields);
this.sublists = Object.entries(this.sublists || {}).reduce((acc, [lineId, lines]) => {
acc[lineId] = {
currentline: {},
lines: [...(("lines" in lines ? lines.lines : lines) || [])],
};
acc[lineId].lines = acc[lineId].lines.map((line) => {
line = { ...line };
line._id = line._id || randomUUID();
Object.entries(line).forEach(([key, value]) => {
if (value instanceof Record) {
line[key] = new Record(value);
} else {
line[key] = structuredClone(value);
}
});
return line;
});
return acc;
}, {});
this.subrecords = Object.entries(this.subrecords || {}).reduce((acc, [subrecordId, subrecord]) => {
acc[subrecordId] = new Record(subrecord);
return acc;
}, {});
};
#getSublist(options) {
const sublist = this.sublists[options.sublistId];
if (!sublist) {
throw new Error("Sublist does not exist");
}
return sublist;
}
#getLine(options) {
const sublist = this.#getSublist(options);
const line = sublist.lines[options.line];
if (!line) {
throw new Error("Line does not exist");
}
return line;
}
cancelLine = (options) => {
this.selectNewLine(options.sublistId);
return this;
};
commitLine = (options) => {
const sublist = this.#getSublist(options);
if (!sublist.currentline._id) {
sublist.currentline._id = randomUUID();
}
sublist.lines[this.getCurrentSublistIndex(options.sublistId)] = sublist.currentline;
this.selectNewLine(options.sublistId);
return this;
};
executeMacro = (options) => {};
findMatrixSublistLineWithValue = (options) => {};
findSublistLineWithValue = (options) => {
for (let i = 0; i < this.getLineCount(options.sublistId); i++) {
if (this.getSublistValue(options.sublistId, options.fieldId, i) == options.value) {
return i;
}
}
return -1;
};
getCurrentMatrixSublistValue = (options) => {};
getCurrentSublistField = (options) => {
const sublist = this.#getSublist(options);
if (options.fieldId in sublist.currentline) {
return new Field({ id: options.fieldId, label: options.fieldId, sublistId: options.sublistId });
}
return null;
};
getCurrentSublistIndex = (options) => {
const sublist = this?.sublists?.[options.sublistId];
if (sublist) {
const existingIndex = sublist?.lines.findIndex((a) => a._id === sublist.currentline._id);
return existingIndex > -1 ? existingIndex : sublist?.lines.length;
}
return -1;
};
getCurrentSublistSubrecord = (options) => {
const sublist = this.#getSublist(options);
if (!(options.fieldId in sublist.currentline) || !(sublist.currentline[options.fieldId] instanceof Record)) {
throw new Error(`Field ${options.fieldId} is not a subrecord field`);
}
return sublist.currentline[options.fieldId];
};
getCurrentSublistText = (options) => {
const sublist = this.#getSublist(options);
const field = sublist.currentline[options.fieldId];
return parseText(field, this.isDynamic, "getCurrentSublistText");
};
getCurrentSublistValue = (options) => {
const sublist = this.sublists[options.sublistId];
// this is correct, suitescript doesn't error when supplying a sublistId that doesn't exist
if (sublist === undefined) {
return null;
}
const field = sublist.currentline[options.fieldId];
return parseValue(field);
};
getField = (options) => {
if (options.fieldId in this.fields) {
return new Field({ id: options.fieldId, label: options.fieldId });
}
return null;
};
getFields = () => {
return Object.keys(this.fields);
};
getLineCount = (options) => {
const sublist = this.sublists[options.sublistId];
if (sublist === undefined) {
return -1;
}
return sublist.lines.length;
};
getMacro = (options) => {};
getMacros = (options) => {};
getMatrixHeaderCount = (options) => {};
getMatrixHeaderField = (options) => {};
getMatrixHeaderValue = (options) => {};
getMatrixSublistField = (options) => {};
getMatrixSublistValue = (options) => {};
getSublist = (options) => {
if (options.sublistId in this.sublists) {
return new Sublist({ id: options.sublistId });
}
return null;
};
getSublists = () => {
return Object.keys(this.sublists);
};
getSublistField = (options) => {
const line = this.#getLine(options);
if (options.fieldId in line) {
return new Field({ id: options.fieldId, label: options.fieldId, sublistId: options.sublistId });
}
return null;
};
getSublistFields = (options) => {
const sublist = this.#getSublist(options);
return Object.keys(sublist.lines[0] || {}).filter((id) => id !== "_id");
};
getSublistSubrecord = (options) => {
const line = this.#getLine(options);
if (!(options.fieldId in line) || !(line[options.fieldId] instanceof Record)) {
throw new Error(`Field ${options.fieldId} is not a subrecord field`);
}
return line[options.fieldId];
};
getSublistText = (options) => {
const line = this.#getLine(options);
const field = line[options.fieldId];
return parseText(field, this.isDynamic, "getSublistText");
};
getSublistValue = (options) => {
const line = this.#getLine(options);
const field = line[options.fieldId];
return parseValue(field);
};
getSubrecord = (options) => {
if (!(options.fieldId in this.subrecords)) {
throw new Error(`Field ${options.fieldId} is not a subrecord field`);
}
return this.subrecords[options.fieldId];
};
getText = (options) => {
const field = this.fields[options.fieldId];
return parseText(field, this.isDynamic, "getText");
};
getValue = (options) => {
const field = this.fields[options.fieldId];
return parseValue(field);
};
hasCurrentSublistSubrecord = (options) => {
return Boolean(this.getCurrentSublistSubrecord(options));
};
hasSublistSubrecord = (options) => {
return Boolean(this.getSublistSubrecord(options));
};
hasSubrecord = (options) => {
return Boolean(this.getSubrecord(options));
};
insertLine = (options) => {
const sublist = this.sublists[options.sublistId];
if (!sublist) {
throw new Error("Sublist does not exist");
}
if (options.line < 0 || options.line > sublist.lines.length) {
throw new Error("Line is outside valid range");
}
sublist.lines.splice(options.line, 0, {});
if (this.isDynamic) {
this.selectLine(options);
}
return this;
};
// @options("sublistId", "from", "to")
// @required("sublistId", "from", "to")
// moveLine = (options) => {
// const sublist = this.#getSublist(options);
// if (options.from < 0 || options.from > sublist.lines.length - 1) {
// throw new Error("From is outside valid range");
// }
// if (options.to < 0 || options.to > sublist.lines.length) {
// throw new Error("To is outside valid range");
// }
// // if (options.to > options.from) {
// // options.to--;
// // }
// const line = sublist.lines.splice(options.from, 1);
// sublist.lines.splice(options.to, 0, line);
// return this;
// };
removeCurrentSublistSubrecord = (options) => {
const sublist = this.#getSublist(options);
if (!(options.fieldId in sublist.currentline) || !(sublist.currentline[options.fieldId] instanceof Record)) {
throw new Error(`Field ${options.fieldId} is not a subrecord field`);
}
sublist.currentline[options.fieldId] = null;
return this;
};
removeLine = (options) => {
const sublist = this.#getSublist(options);
this.#getLine(options);
sublist.lines.splice(options.line, 1);
if (this.isDynamic) {
if (sublist.lines.length > 0) {
this.selectLine(options.sublistId, 0);
} else {
this.selectNewLine(options.sublistId);
}
}
return this;
};
removeSublistSubrecord = (options) => {
const line = this.#getLine(options);
if (!(options.fieldId in line) || !(line[options.fieldId] instanceof Record)) {
throw new Error(`Field ${options.fieldId} is not a subrecord field`);
}
line[options.fieldId] = null;
return this;
};
removeSubrecord = (options) => {
if (!(options.fieldId in this.subrecords)) {
throw new Error(`Field ${options.fieldId} is not a subrecord field`);
}
this.subrecords[options.fieldId] = null;
return this;
};
save = (options) => {
if (this.id && SuiteScriptMocks.records.get(this).version !== this.version) {
throw new Error("Record has changed");
}
this.version++;
const copy = new Record(this);
// change fields that only have value to not be an object so getText works
Object.entries(copy.fields).forEach(([key, value]) => {
if (typeof value === "object" && value !== null && !(value instanceof Date) && !("text" in value)) {
copy.fields[key] = value.value;
}
});
Object.values(copy.sublists).forEach((sublist) => {
sublist.lines.forEach((line) => {
Object.entries(line).forEach(([key, value]) => {
if (typeof value === "object" && value !== null && !(value instanceof Date) && !("text" in value)) {
line[key] = value.value;
}
});
});
});
if (!this.id) {
this.id = copy.id = Math.max(...Array.from(SuiteScriptMocks.records.values()).map((a) => a.id)) + 1;
SuiteScriptMocks.createdRecords.push(copy);
}
SuiteScriptMocks.records.set(copy);
SuiteScriptMocks.savedRecords.push(copy);
return this.id;
};
selectLine = (options) => {
const sublist = this.#getSublist(options);
if (options.line != this.getCurrentSublistIndex(options.sublistId)) {
const line = this.#getLine(options);
sublist.currentline = { ...line };
sublist.lines = sublist.lines.filter((a) => a._id);
}
return this;
};
selectNewLine = (options) => {
const sublist = this.#getSublist(options);
sublist.currentline = {};
sublist.lines = sublist.lines.filter((a) => a._id);
return this;
};
setCurrentMatrixSublistValue = (options) => {};
setCurrentSublistText = (options) => {
const sublist = this.#getSublist(options);
sublist.currentline[options.fieldId] = { value: options.text, text: options.text };
return this;
};
setCurrentSublistValue = (options) => {
const sublist = this.#getSublist(options);
sublist.currentline[options.fieldId] = { value: options.value };
return this;
};
setMatrixHeaderValue = (options) => {};
setMatrixSublistValue = (options) => {};
setSublistText = (options) => {
const line = this.#getLine(options);
line[options.fieldId] = { value: options.text, text: options.text };
return this;
};
setSublistValue = (options) => {
const line = this.#getLine(options);
line[options.fieldId] = { value: options.value };
return this;
};
setText = (options) => {
this.fields[options.fieldId] = { value: options.text, text: options.text };
return this;
};
setValue = (options) => {
this.fields[options.fieldId] = { value: options.value };
return this;
};
}
function parseValue(field) {
if (Array.isArray(field)) {
return field;
} else if (
typeof field === "object" &&
field !== null &&
Object.prototype.toString.call(field) !== "[object Date]"
) {
return field.value;
}
return field;
}
function parseText(field, isDynamic, funcName) {
if (Object.prototype.toString.call(field) === "[object Date]") {
return datefns.format(field, SuiteScriptMocks.dateFormat);
} else if (Array.isArray(field)) {
return String(field);
} else if (typeof field === "object" && field !== null) {
if (!isDynamic && !("text" in field)) {
throw new Error(`Cannot use ${funcName} on field that has had value but not text set in standard mode`);
}
return field.text || field.value;
}
return field;
}
module.exports = Record;