Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* text=auto eol=lf

*.bat eol=crlf
*.ps1 eol=crlf
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "20.x"
node-version: "24.x"
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm publish --tag latest --provenance --access public
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: test

on:
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- "22.x"
- "24.x"
- "26.x"

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm

- run: npm ci
- run: npm test
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Export support:
- [Orleans9](https://blog.csdn.net/zinchliang) - [article](https://blog.csdn.net/zinchliang/article/details/120262185)

## Contribution
1. Install node v20.17.0
1. Install node v24
3. `npm run prepare`
4. `npm run build-storybook`
5. `npm run storybook`
Expand Down
10,509 changes: 5,984 additions & 4,525 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"@fortune-sheet/react": "^0.19.2",
"@fortune-sheet/react": "^1.0.4",
"@storybook/addon-essentials": "^7.6.12",
"@storybook/addon-interactions": "^7.6.12",
"@storybook/addon-links": "^7.6.12",
Expand All @@ -32,6 +32,7 @@
"@storybook/react-webpack5": "^7.3.2",
"@storybook/test": "^7.6.12",
"@types/node": "^20.11.16",
"jsdom": "^26.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook": "^7.6.16",
Expand All @@ -47,6 +48,7 @@
},
"scripts": {
"prepare": "tsc",
"test": "npm run prepare && node --test test/transformExcelToFortune.xls_preview.test.js",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
Expand Down
9 changes: 8 additions & 1 deletion src/ToFortuneSheet/FortuneFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,14 @@ export class FortuneFile {
}

if (sheet.images != null) {
sheetout.images = sheet.images;
sheetout.images = Object.entries(sheet.images).map(([id, image]: any) => ({
...image,
id,
left: image.default?.left ?? 0,
top: image.default?.top ?? 0,
width: image.default?.width ?? image.originWidth ?? 0,
height: image.default?.height ?? image.originHeight ?? 0,
}));
}

if (sheet.dataVerification != null) {
Expand Down
2 changes: 1 addition & 1 deletion src/ToFortuneSheet/IFortune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export interface IfortuneSheet {
export interface IfortuneSheetSelection {
row: number[]; //selection start row and end row
column: number[]; //selection start column and end column
sheetIndex: number;
sheetIndex: string;
}

export interface IfortuneSheetChart {}
Expand Down
8 changes: 4 additions & 4 deletions src/common/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,20 @@ export function getcellrange(

let sheettxt = "",
rangetxt = "",
sheetIndex = -1;
sheetIndex = sheetId;

if (val.length > 1) {
sheettxt = val[0];
rangetxt = val[1];

let si = sheets[sheettxt];
if (si == null) {
sheetIndex = parseInt(sheetId);
sheetIndex = sheetId;
} else {
sheetIndex = parseInt(si);
sheetIndex = si;
}
} else {
sheetIndex = parseInt(sheetId);
sheetIndex = sheetId;
rangetxt = val[0];
}

Expand Down
Binary file added test/fixtures/xls_preview.xlsx
Binary file not shown.
230 changes: 230 additions & 0 deletions test/transformExcelToFortune.xls_preview.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs/promises");
const path = require("node:path");
const React = require("react");
const ReactDOMClient = require("react-dom/client");
const { JSDOM } = require("jsdom");

const { transformExcelToFortune } = require("../dist/main.js");

const fixturePath = path.resolve(__dirname, "fixtures", "xls_preview.xlsx");

const toBuffer = (part) => {
if (Buffer.isBuffer(part)) {
return part;
}

if (part instanceof ArrayBuffer) {
return Buffer.from(part);
}

if (ArrayBuffer.isView(part)) {
return Buffer.from(part.buffer, part.byteOffset, part.byteLength);
}

if (typeof part === "string") {
return Buffer.from(part);
}

throw new TypeError(`Unsupported file part: ${typeof part}`);
};

global.File = function TestFile(parts, name, options = {}) {
const buffer = Buffer.concat(parts.map(toBuffer));
buffer.name = name;
buffer.type = options.type ?? "";
buffer.lastModified = options.lastModified ?? Date.now();
buffer.arrayBuffer = async () =>
buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength
);
buffer.text = async () => buffer.toString();
return buffer;
};

global.window = {
navigator: {
userAgent: "node-test",
},
};

const waitForDeferredSizing = () =>
new Promise((resolve) => {
setTimeout(resolve, 10);
});

const getCell = (sheet, row, column) =>
sheet.celldata.find((cell) => cell.r === row && cell.c === column);

const loadFixtureIntoFortune = async () => {
const fileBuffer = await fs.readFile(fixturePath);
const file = new File(
[fileBuffer],
"xls_preview.xlsx",
{
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}
);

const setSheetsCalls = [];
const setKeyCalls = [];
const columnWidthCalls = [];
const rowHeightCalls = [];

const setSheets = (sheets) => {
setSheetsCalls.push(sheets);
};

const setKey = (updater) => {
setKeyCalls.push(updater);
};

const sheetRef = {
current: {
setColumnWidth: (config, meta) => {
columnWidthCalls.push([config, meta]);
},
setRowHeight: (config, meta) => {
rowHeightCalls.push([config, meta]);
},
},
};

await transformExcelToFortune(file, setSheets, setKey, sheetRef);
await waitForDeferredSizing();

return {
setSheetsCalls,
setKeyCalls,
columnWidthCalls,
rowHeightCalls,
};
};

test("transformExcelToFortune converts xls_preview.xlsx into Fortune sheets", async () => {
const {
setSheetsCalls,
setKeyCalls,
columnWidthCalls,
rowHeightCalls,
} = await loadFixtureIntoFortune();

assert.equal(setSheetsCalls.length, 1);
assert.equal(setKeyCalls.length, 1);
assert.equal(typeof setKeyCalls[0], "function");
assert.equal(setKeyCalls[0](0), 1);

const [sheets] = setSheetsCalls;
assert.equal(sheets.length, 1);

const [sheet] = sheets;
assert.equal(sheet.name, "Feuille1");
assert.equal(sheet.status, "1");
assert.equal(sheet.order, "0");
assert.equal(sheet.showGridLines, "1");
assert.deepEqual(sheet.luckysheet_select_save, [
{
row: [10, 10],
column: [5, 5],
sheetIndex: "1",
},
]);

const b2 = getCell(sheet, 1, 1);
assert.ok(b2);
assert.equal(b2.v.v, "552150");

const c2 = getCell(sheet, 1, 2);
assert.ok(c2);
assert.equal(c2.v.f, "=$B$4");
assert.equal(c2.v.v, "552150");

const i3 = getCell(sheet, 2, 8);
assert.ok(i3);
assert.equal(i3.v.v, "max");

const p7 = getCell(sheet, 6, 15);
assert.ok(p7);
assert.equal(p7.v.f, "=N7-O7");
assert.equal(p7.v.v, "1300");

const images = sheet.images || [];
assert.equal(images.length, 1);
assert.match(images[0].src, /^data:image\/png;base64,/);
assert.equal(images[0].id.startsWith("image_"), true);
assert.equal(images[0].left, 116.13333333333333);
assert.equal(images[0].top, 312.2);
assert.equal(images[0].width, 252.73333333333335);
assert.equal(images[0].height, 273.06666666666666);
assert.equal(images[0].type, "2");
assert.equal(images[0].fromCol, 1);
assert.equal(images[0].fromRow, 11);
assert.equal(images[0].toCol, 4);
assert.equal(images[0].toRow, 21);

assert.equal(columnWidthCalls.length, 1);
assert.deepEqual(columnWidthCalls[0], [sheet.config.columnlen || {}, { id: sheet.id }]);

assert.equal(rowHeightCalls.length, 1);
assert.deepEqual(rowHeightCalls[0], [sheet.config.rowlen || {}, { id: sheet.id }]);
});

test("converted xls_preview.xlsx sheets can be mounted in Workbook", async () => {
const { setSheetsCalls } = await loadFixtureIntoFortune();
const [sheets] = setSheetsCalls;
const { Workbook } = await import("@fortune-sheet/react/dist/index.esm.js");
const dom = new JSDOM(
"<!doctype html><html><body><div id='root'></div></body></html>",
{ pretendToBeVisual: true, url: "http://localhost/" }
);

const previousGlobals = {
window: global.window,
document: global.document,
navigator: global.navigator,
HTMLElement: global.HTMLElement,
MutationObserver: global.MutationObserver,
getComputedStyle: global.getComputedStyle,
requestAnimationFrame: global.requestAnimationFrame,
cancelAnimationFrame: global.cancelAnimationFrame,
ResizeObserver: global.ResizeObserver,
DOMParser: global.DOMParser,
};

global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
global.HTMLElement = dom.window.HTMLElement;
global.MutationObserver = dom.window.MutationObserver;
global.getComputedStyle = dom.window.getComputedStyle;
global.requestAnimationFrame = dom.window.requestAnimationFrame.bind(dom.window);
global.cancelAnimationFrame = dom.window.cancelAnimationFrame.bind(dom.window);
global.ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};
global.DOMParser = dom.window.DOMParser;

const root = ReactDOMClient.createRoot(document.getElementById("root"));

try {
root.render(React.createElement(Workbook, { data: sheets }));
await new Promise((resolve) => setTimeout(resolve, 50));
} finally {
root.unmount();
dom.window.close();
global.window = previousGlobals.window;
global.document = previousGlobals.document;
global.navigator = previousGlobals.navigator;
global.HTMLElement = previousGlobals.HTMLElement;
global.MutationObserver = previousGlobals.MutationObserver;
global.getComputedStyle = previousGlobals.getComputedStyle;
global.requestAnimationFrame = previousGlobals.requestAnimationFrame;
global.cancelAnimationFrame = previousGlobals.cancelAnimationFrame;
global.ResizeObserver = previousGlobals.ResizeObserver;
global.DOMParser = previousGlobals.DOMParser;
}
});
Loading