Use C8 for coverage

This commit is contained in:
Dolan Miu
2023-06-12 18:43:29 +01:00
parent 09ab91eeef
commit eeee01a0e0
11 changed files with 655 additions and 2090 deletions

View File

@ -17,7 +17,6 @@
"dolan", "dolan",
"execa", "execa",
"falsey", "falsey",
"fflate",
"iife", "iife",
"Initializable", "Initializable",
"iroha", "iroha",

3
.gitignore vendored
View File

@ -59,6 +59,3 @@ My Document.docx
# Temporary folder # Temporary folder
tmp tmp
# nyc
.nyc_output

25
.nycrc
View File

@ -1,25 +0,0 @@
{
"check-coverage": true,
"statements": 99.87,
"branches": 98.21,
"functions": 100,
"lines": 99.86,
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts",
"src/import-dotx/import-dotx.ts"
],
"reporter": [
"lcov",
"text",
"json"
],
"extension": [
".ts"
],
"cache": true,
"all": true,
"sourceMap": true
}

2503
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,9 +22,8 @@
], ],
"scripts": { "scripts": {
"build": "tsc && vite build", "build": "tsc && vite build",
"test": "vitest run", "test": "vitest",
"test.coverage": "nyc npm test", "test.coverage": "vitest run --coverage",
"test.watch": "vitest",
"prepublishOnly": "npm run build --omit=dev", "prepublishOnly": "npm run build --omit=dev",
"lint": "eslint --ext .ts src", "lint": "eslint --ext .ts src",
"predemo": "npm run build", "predemo": "npm run build",
@ -72,16 +71,13 @@
}, },
"homepage": "https://docx.js.org", "homepage": "https://docx.js.org",
"devDependencies": { "devDependencies": {
"@types/chai": "^4.2.15",
"@types/chai-as-promised": "^7.1.5",
"@types/inquirer": "^9.0.3", "@types/inquirer": "^9.0.3",
"@types/prompt": "^1.1.1", "@types/prompt": "^1.1.1",
"@types/unzipper": "^0.10.4", "@types/unzipper": "^0.10.4",
"@types/xml": "^1.0.8", "@types/xml": "^1.0.8",
"@typescript-eslint/eslint-plugin": "^5.36.1", "@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1", "@typescript-eslint/parser": "^5.36.1",
"chai": "^4.3.6", "@vitest/coverage-c8": "^0.32.0",
"chai-as-promised": "^7.1.1",
"cspell": "^6.2.2", "cspell": "^6.2.2",
"docsify-cli": "^4.3.0", "docsify-cli": "^4.3.0",
"eslint": "^8.23.0", "eslint": "^8.23.0",
@ -95,7 +91,6 @@
"glob": "^9.3.0", "glob": "^9.3.0",
"inquirer": "^9.2.7", "inquirer": "^9.2.7",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"nyc": "^15.1.0",
"pre-commit": "^1.2.2", "pre-commit": "^1.2.2",
"prettier": "^2.3.1", "prettier": "^2.3.1",
"process": "^0.11.10", "process": "^0.11.10",

View File

@ -1,6 +1,4 @@
/* tslint:disable:typedef space-before-function-paren */
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import * as fflate from "fflate";
import { File } from "@file/file"; import { File } from "@file/file";
import { Footer, Header } from "@file/header"; import { Footer, Header } from "@file/header";
@ -9,21 +7,6 @@ import * as convenienceFunctions from "@util/convenience-functions";
import { Compiler } from "./next-compiler"; import { Compiler } from "./next-compiler";
const unzip = (zipFile: Uint8Array): Promise<ReadonlySet<string>> => {
const set = new Set<string>();
const unzipper = new fflate.Unzip((file) => {
set.add(file.name);
});
return new Promise<ReadonlySet<string>>((resolve) => {
setTimeout(() => {
resolve(set);
}, 1000);
unzipper.push(zipFile, true);
});
};
describe("Compiler", () => { describe("Compiler", () => {
let compiler: Compiler; let compiler: Compiler;
@ -42,18 +25,18 @@ describe("Compiler", () => {
describe("#compile()", () => { describe("#compile()", () => {
it( it(
"should pack all the content", "should pack all the content",
async () => { () => {
const file = new File({ const file = new File({
sections: [], sections: [],
comments: { comments: {
children: [], children: [],
}, },
}); });
const zipFile = await compiler.compile(file); const zipFile = compiler.compile(file);
const fileNames = await unzip(zipFile); const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).has.length(13); expect(fileNames).is.an.instanceof(Array);
expect(fileNames).to.include("word/document.xml"); expect(fileNames).has.length(17);
expect(fileNames).to.include("word/document.xml"); expect(fileNames).to.include("word/document.xml");
expect(fileNames).to.include("word/styles.xml"); expect(fileNames).to.include("word/styles.xml");
expect(fileNames).to.include("docProps/core.xml"); expect(fileNames).to.include("docProps/core.xml");
@ -75,7 +58,7 @@ describe("Compiler", () => {
it( it(
"should pack all additional headers and footers", "should pack all additional headers and footers",
async () => { () => {
const file = new File({ const file = new File({
sections: [ sections: [
{ {
@ -107,10 +90,12 @@ describe("Compiler", () => {
], ],
}); });
const zipFile = await compiler.compile(file); const zipFile = compiler.compile(file);
const fileNames = await unzip(zipFile); const fileNames = Object.keys(zipFile.files).map((f) => zipFile.files[f].name);
expect(fileNames).is.an.instanceof(Array);
expect(fileNames).has.length(25);
expect(fileNames).has.length(21);
expect(fileNames).to.include("word/header1.xml"); expect(fileNames).to.include("word/header1.xml");
expect(fileNames).to.include("word/_rels/header1.xml.rels"); expect(fileNames).to.include("word/_rels/header1.xml.rels");
expect(fileNames).to.include("word/header2.xml"); expect(fileNames).to.include("word/header2.xml");

View File

@ -1,4 +1,4 @@
import { strToU8, zip } from "fflate"; import JSZip from "jszip";
import xml from "xml"; import xml from "xml";
import { File } from "@file/file"; import { File } from "@file/file";
@ -44,39 +44,26 @@ export class Compiler {
this.numberingReplacer = new NumberingReplacer(); this.numberingReplacer = new NumberingReplacer();
} }
public compile(file: File, prettifyXml?: PrettifyType): Promise<Uint8Array> { public compile(file: File, prettifyXml?: PrettifyType): JSZip {
const zip = new JSZip();
const xmlifiedFileMapping = this.xmlifyFile(file, prettifyXml); const xmlifiedFileMapping = this.xmlifyFile(file, prettifyXml);
const map = new Map<string, IXmlifyedFile | readonly IXmlifyedFile[]>(Object.entries(xmlifiedFileMapping)); const map = new Map<string, IXmlifyedFile | readonly IXmlifyedFile[]>(Object.entries(xmlifiedFileMapping));
return new Promise<Uint8Array>((resolve, reject) => { for (const [, obj] of map) {
zip(
{
...Array.from(map.entries()).reduce((acc, [, obj]) => {
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
for (const subFile of obj as readonly IXmlifyedFile[]) { for (const subFile of obj as readonly IXmlifyedFile[]) {
// eslint-disable-next-line functional/immutable-data zip.file(subFile.path, subFile.data);
acc[subFile.path] = strToU8(subFile.data);
} }
} else { } else {
// eslint-disable-next-line functional/immutable-data zip.file((obj as IXmlifyedFile).path, (obj as IXmlifyedFile).data);
acc[(obj as IXmlifyedFile).path] = strToU8((obj as IXmlifyedFile).data); }
} }
return acc; for (const { stream, fileName } of file.Media.Array) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any zip.file(`word/media/${fileName}`, stream);
}, {} as any),
...file.Media.Array.reduce((acc, { stream, fileName }) => ({ ...acc, [`word/media/${fileName}`]: stream }), {}),
},
{},
(err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
} }
},
); return zip;
});
} }
private xmlifyFile(file: File, prettify?: PrettifyType): IXmlifyedFileMapping { private xmlifyFile(file: File, prettify?: PrettifyType): IXmlifyedFileMapping {

View File

@ -3,7 +3,7 @@ import { afterEach, assert, beforeEach, describe, expect, it, vi } from "vitest"
import { File } from "@file/file"; import { File } from "@file/file";
import { HeadingLevel, Paragraph } from "@file/paragraph"; import { HeadingLevel, Paragraph } from "@file/paragraph";
import { Packer } from "./packer"; import { Packer, PrettifyType } from "./packer";
describe("Packer", () => { describe("Packer", () => {
let file: File; let file: File;
@ -35,6 +35,36 @@ describe("Packer", () => {
}); });
}); });
describe("prettify", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("should use a default prettify value", async () => {
const spy = vi.spyOn((Packer as any).compiler, "compile");
await Packer.toString(file, true);
expect(spy).toBeCalledWith(expect.anything(), PrettifyType.WITH_2_BLANKS);
});
it("should use a prettify value", async () => {
const spy = vi.spyOn((Packer as any).compiler, "compile");
await Packer.toString(file, PrettifyType.WITH_4_BLANKS);
expect(spy).toBeCalledWith(expect.anything(), PrettifyType.WITH_4_BLANKS);
});
it("should use an undefined prettify value", async () => {
const spy = vi.spyOn((Packer as any).compiler, "compile");
await Packer.toString(file, false);
expect(spy).toBeCalledWith(expect.anything(), undefined);
});
});
describe("#toString()", () => { describe("#toString()", () => {
it("should return a non-empty string", async () => { it("should return a non-empty string", async () => {
const result = await Packer.toString(file); const result = await Packer.toString(file);
@ -133,7 +163,10 @@ describe("Packer", () => {
describe("#toStream()", () => { describe("#toStream()", () => {
it("should create a standard docx file", async () => { it("should create a standard docx file", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
vi.spyOn((Packer as any).compiler, "compile").mockReturnValue(Promise.resolve(new Uint8Array(255))); vi.spyOn((Packer as any).compiler, "compile").mockReturnValue({
// tslint:disable-next-line: no-empty
generateAsync: () => Promise.resolve(vi.fn()),
});
const stream = Packer.toStream(file); const stream = Packer.toStream(file);
const p = new Promise<void>((resolve, reject) => { const p = new Promise<void>((resolve, reject) => {

View File

@ -1,6 +1,5 @@
import { Stream } from "stream"; import { Stream } from "stream";
import { File } from "@file/file"; import { File } from "@file/file";
import { strFromU8 } from "fflate";
import { Compiler } from "./next-compiler"; import { Compiler } from "./next-compiler";
@ -14,46 +13,63 @@ export enum PrettifyType {
WITH_TAB = "\t", WITH_TAB = "\t",
} }
const convertPrettifyType = (prettify?: boolean | PrettifyType): PrettifyType | undefined =>
prettify === true ? PrettifyType.WITH_2_BLANKS : prettify === false ? undefined : prettify;
export class Packer { export class Packer {
public static async toString(file: File, prettify?: boolean | PrettifyType): Promise<string> { public static async toString(file: File, prettify?: boolean | PrettifyType): Promise<string> {
const zip = await this.compiler.compile( const zip = this.compiler.compile(file, convertPrettifyType(prettify));
file, const zipData = await zip.generateAsync({
prettify === true ? PrettifyType.WITH_2_BLANKS : prettify === false ? undefined : prettify, type: "string",
); mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
return strFromU8(zip); compression: "DEFLATE",
});
return zipData;
} }
public static async toBuffer(file: File, prettify?: boolean | PrettifyType): Promise<Buffer> { public static async toBuffer(file: File, prettify?: boolean | PrettifyType): Promise<Buffer> {
const zip = await this.compiler.compile( const zip = this.compiler.compile(file, convertPrettifyType(prettify));
file, const zipData = await zip.generateAsync({
prettify === true ? PrettifyType.WITH_2_BLANKS : prettify === false ? undefined : prettify, type: "nodebuffer",
); mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
return Buffer.from(zip.buffer); compression: "DEFLATE",
});
return zipData;
} }
public static async toBase64String(file: File, prettify?: boolean | PrettifyType): Promise<string> { public static async toBase64String(file: File, prettify?: boolean | PrettifyType): Promise<string> {
const zip = await this.compiler.compile( const zip = this.compiler.compile(file, convertPrettifyType(prettify));
file, const zipData = await zip.generateAsync({
prettify === true ? PrettifyType.WITH_2_BLANKS : prettify === false ? undefined : prettify, type: "base64",
); mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
compression: "DEFLATE",
});
return Promise.resolve(strFromU8(zip)); return zipData;
} }
public static async toBlob(file: File, prettify?: boolean | PrettifyType): Promise<Blob> { public static async toBlob(file: File, prettify?: boolean | PrettifyType): Promise<Blob> {
const zip = await this.compiler.compile( const zip = this.compiler.compile(file, convertPrettifyType(prettify));
file, const zipData = await zip.generateAsync({
prettify === true ? PrettifyType.WITH_2_BLANKS : prettify === false ? undefined : prettify, type: "blob",
); mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
compression: "DEFLATE",
});
return new Blob([zip.buffer]); return zipData;
} }
public static toStream(file: File, prettify?: boolean | PrettifyType): Stream { public static toStream(file: File, prettify?: boolean | PrettifyType): Stream {
const zip = this.compiler.compile(file, prettify === true ? PrettifyType.WITH_2_BLANKS : prettify === false ? undefined : prettify);
const stream = new Stream(); const stream = new Stream();
const zip = this.compiler.compile(file, convertPrettifyType(prettify));
zip.then((z) => { zip.generateAsync({
type: "nodebuffer",
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
compression: "DEFLATE",
}).then((z) => {
stream.emit("data", z); stream.emit("data", z);
stream.emit("end"); stream.emit("end");
}); });

View File

@ -1,14 +1,10 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as chai from "chai";
import JSZip from "jszip"; import JSZip from "jszip";
import chaiAsPromised from "chai-as-promised";
import { ExternalHyperlink, ImageRun, Paragraph, TextRun } from "@file/paragraph"; import { ExternalHyperlink, ImageRun, Paragraph, TextRun } from "@file/paragraph";
import { patchDocument, PatchType } from "./from-docx"; import { patchDocument, PatchType } from "./from-docx";
chai.use(chaiAsPromised);
const MOCK_XML = ` const MOCK_XML = `
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" <w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas"

View File

@ -22,6 +22,7 @@ export default defineConfig({
}, },
}, },
build: { build: {
minify: false,
lib: { lib: {
entry: [resolve(__dirname, "src/index.ts")], entry: [resolve(__dirname, "src/index.ts")],
name: "docx", name: "docx",
@ -35,5 +36,13 @@ export default defineConfig({
}, },
test: { test: {
environment: "jsdom", environment: "jsdom",
coverage: {
provider: "c8",
reporter: ["text", "json", "html"],
statements: 99.93,
branches: 98.85,
functions: 100,
lines: 99.93,
},
}, },
}); });