implement phase 4
This commit is contained in:
parent
25c2ce53cd
commit
f4b1c3e14b
27 changed files with 6929 additions and 48 deletions
195
addin/src/services/metadataService.test.ts
Normal file
195
addin/src/services/metadataService.test.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
import {
|
||||
ADDIN_SIGNATURE_GUID,
|
||||
buildVersionHistoryXml,
|
||||
CUSTOM_XML_NAMESPACE,
|
||||
isTrackedDocument,
|
||||
readContentControlFields,
|
||||
readTrackedProperties,
|
||||
readVersionHistoryFromXml,
|
||||
stampTrackedProperties,
|
||||
writeVersionHistoryToXml,
|
||||
XmlVersionEntry,
|
||||
} from "./metadataService";
|
||||
import { installWordMock } from "../test/wordMock";
|
||||
|
||||
describe("readTrackedProperties", () => {
|
||||
it("อ่านค่าได้ครบทั้ง 4 properties เมื่อมีทั้งหมด", async () => {
|
||||
installWordMock({
|
||||
properties: {
|
||||
SavedByAddIn: ADDIN_SIGNATURE_GUID,
|
||||
DocumentTrackingId: "11111111-1111-1111-1111-111111111111",
|
||||
LastVersionId: "22222222-2222-2222-2222-222222222222",
|
||||
LastVersionTimestamp: "2026-08-19T10:00:00Z",
|
||||
},
|
||||
});
|
||||
|
||||
await expect(readTrackedProperties()).resolves.toEqual({
|
||||
savedByAddIn: ADDIN_SIGNATURE_GUID,
|
||||
documentTrackingId: "11111111-1111-1111-1111-111111111111",
|
||||
lastVersionId: "22222222-2222-2222-2222-222222222222",
|
||||
lastVersionTimestamp: "2026-08-19T10:00:00Z",
|
||||
});
|
||||
});
|
||||
|
||||
it("property ที่ไม่มีในเอกสารเป็น undefined ไม่ throw", async () => {
|
||||
installWordMock({ properties: { UnrelatedProperty: "x" } });
|
||||
|
||||
const properties = await readTrackedProperties();
|
||||
expect(properties.savedByAddIn).toBeUndefined();
|
||||
expect(properties.documentTrackingId).toBeUndefined();
|
||||
expect(properties.lastVersionId).toBeUndefined();
|
||||
expect(properties.lastVersionTimestamp).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("isTrackedDocument (FR-1)", () => {
|
||||
it("true เมื่อ SavedByAddIn ตรง ADDIN_SIGNATURE_GUID", async () => {
|
||||
installWordMock({ properties: { SavedByAddIn: ADDIN_SIGNATURE_GUID } });
|
||||
await expect(isTrackedDocument()).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("false เมื่อค่าไม่ตรง หรือไม่มี property", async () => {
|
||||
installWordMock({ properties: { SavedByAddIn: "OTHER-ADDIN-GUID" } });
|
||||
await expect(isTrackedDocument()).resolves.toBe(false);
|
||||
|
||||
installWordMock({ properties: {} });
|
||||
await expect(isTrackedDocument()).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("stampTrackedProperties (FR-2 ขั้นตอนที่ 2)", () => {
|
||||
it("stamp ครบ 4 properties ด้วย add() ทับค่าเดิมได้", async () => {
|
||||
const state = installWordMock({
|
||||
properties: { SavedByAddIn: "OTHER-ADDIN-GUID", DocumentTrackingId: "old-tracking-id" },
|
||||
});
|
||||
|
||||
await stampTrackedProperties({
|
||||
documentTrackingId: "new-tracking-id",
|
||||
lastVersionId: "version-2",
|
||||
lastVersionTimestamp: "2026-08-19T11:00:00Z",
|
||||
});
|
||||
|
||||
expect(state.propertyAdds).toEqual([
|
||||
{ key: "SavedByAddIn", value: ADDIN_SIGNATURE_GUID },
|
||||
{ key: "DocumentTrackingId", value: "new-tracking-id" },
|
||||
{ key: "LastVersionId", value: "version-2" },
|
||||
{ key: "LastVersionTimestamp", value: "2026-08-19T11:00:00Z" },
|
||||
]);
|
||||
// add() ของ property ที่มีอยู่ = update ค่าในเอกสารจริง
|
||||
expect(state.properties.SavedByAddIn).toBe(ADDIN_SIGNATURE_GUID);
|
||||
expect(state.properties.DocumentTrackingId).toBe("new-tracking-id");
|
||||
});
|
||||
|
||||
it("ไม่ส่งค่าใดมา = stamp เฉพาะ SavedByAddIn", async () => {
|
||||
const state = installWordMock({});
|
||||
|
||||
await stampTrackedProperties({});
|
||||
|
||||
expect(state.propertyAdds).toEqual([{ key: "SavedByAddIn", value: ADDIN_SIGNATURE_GUID }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Custom XML Part (delete-then-add, silent fail)", () => {
|
||||
const entries: XmlVersionEntry[] = [
|
||||
{ id: "version-3", number: 3, timestamp: "2026-08-19T12:00:00Z", user: "tester" },
|
||||
{ id: "version-2", number: 2, timestamp: "2026-08-19T11:00:00Z", user: "other" },
|
||||
];
|
||||
|
||||
it("writeVersionHistoryToXml ลบ part เดิมทั้งหมดก่อน add ของใหม่ แล้วคืน true", async () => {
|
||||
const state = installWordMock({
|
||||
xmlParts: [
|
||||
buildVersionHistoryXml([{ id: "version-1", number: 1, timestamp: "t", user: "u" }]),
|
||||
],
|
||||
});
|
||||
|
||||
await expect(writeVersionHistoryToXml(entries)).resolves.toBe(true);
|
||||
|
||||
// อันเดิมถูกลบ เหลืออันใหม่อันเดียว (อยู่ใน namespace ของเรา)
|
||||
expect(state.xmlParts).toHaveLength(1);
|
||||
expect(state.xmlParts[0].namespace).toBe(CUSTOM_XML_NAMESPACE);
|
||||
expect(state.xmlParts[0].xml).toContain('id="version-3"');
|
||||
expect(state.xmlParts[0].xml).toContain('number="3"');
|
||||
expect(state.xmlParts[0].xml).toContain('user="tester"');
|
||||
});
|
||||
|
||||
it("เขียนไม่ได้ (known bug บน Mac) → คืน false ไม่ throw (AC-10)", async () => {
|
||||
installWordMock({ failXmlAdd: true });
|
||||
|
||||
await expect(writeVersionHistoryToXml(entries)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it("readVersionHistoryFromXml parse รายการและเรียงใหม่ → เก่า", async () => {
|
||||
installWordMock({
|
||||
xmlParts: [
|
||||
buildVersionHistoryXml([
|
||||
{ id: "version-1", number: 1, timestamp: "2026-08-19T09:00:00Z", user: "a" },
|
||||
{ id: "version-3", number: 3, timestamp: "2026-08-19T12:00:00Z", user: "b" },
|
||||
{ id: "version-2", number: 2, timestamp: "2026-08-19T11:00:00Z", user: "c" },
|
||||
]),
|
||||
],
|
||||
});
|
||||
|
||||
const result = await readVersionHistoryFromXml();
|
||||
expect(result.map((entry) => entry.number)).toEqual([3, 2, 1]);
|
||||
expect(result[0]).toEqual({
|
||||
id: "version-3",
|
||||
number: 3,
|
||||
timestamp: "2026-08-19T12:00:00Z",
|
||||
user: "b",
|
||||
});
|
||||
});
|
||||
|
||||
it("อ่าน fail → คืน [] ไม่ throw (XML part เป็น secondary เท่านั้น)", async () => {
|
||||
installWordMock({ failXmlRead: true });
|
||||
await expect(readVersionHistoryFromXml()).resolves.toEqual([]);
|
||||
});
|
||||
|
||||
it("buildVersionHistoryXml escape อักขระพิเศษใน attribute", () => {
|
||||
const xml = buildVersionHistoryXml([
|
||||
{ id: 'a"b', number: 1, timestamp: "<t>&", user: "u'ser" },
|
||||
]);
|
||||
expect(xml).toContain('id="a"b"');
|
||||
expect(xml).toContain('timestamp="<t>&"');
|
||||
expect(xml).toContain('user="u'ser"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("readContentControlFields (FR-11)", () => {
|
||||
it("แปลง contentControls เป็น map tag → text", async () => {
|
||||
installWordMock({
|
||||
contentControls: [
|
||||
{ tag: "employee_name", text: "สมชาย ใจดี" },
|
||||
{ tag: "salary", text: "50000" },
|
||||
{ tag: "start_date", text: "2026-08-01" },
|
||||
],
|
||||
});
|
||||
|
||||
await expect(readContentControlFields()).resolves.toEqual({
|
||||
employee_name: "สมชาย ใจดี",
|
||||
salary: "50000",
|
||||
start_date: "2026-08-01",
|
||||
});
|
||||
});
|
||||
|
||||
it("tag ซ้ำเอาตัวแรกตามลำดับในเอกสาร / tag ว่างข้าม", async () => {
|
||||
installWordMock({
|
||||
contentControls: [
|
||||
{ tag: "position", text: "first" },
|
||||
{ tag: "position", text: "second" },
|
||||
{ tag: "", text: "ไม่มี tag" },
|
||||
{ tag: " ", text: "tag วรรคล้วน" },
|
||||
{ tag: "note", text: "ok" },
|
||||
],
|
||||
});
|
||||
|
||||
await expect(readContentControlFields()).resolves.toEqual({
|
||||
position: "first",
|
||||
note: "ok",
|
||||
});
|
||||
});
|
||||
|
||||
it("เอกสารไม่มี content controls → คืน {} ไม่ error", async () => {
|
||||
installWordMock({ contentControls: [] });
|
||||
await expect(readContentControlFields()).resolves.toEqual({});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue