Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@visactor/openinula-vchart",
"comment": "fix: honor the data prop when spec is supplied, including id-less updates and restoring spec.data when the prop is removed",
"type": "patch"
}
],
"packageName": "@visactor/openinula-vchart"
}
9 changes: 9 additions & 0 deletions packages/openinula-vchart/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ module.exports = {
},
parserOptions: { tsconfigRootDir: __dirname, project: './tsconfig.eslint.json' },
// ignorePatterns: [],
overrides: [
{
files: ['jest.config.js', 'jest.setup.js'],
parserOptions: {
ecmaVersion: 2020,
sourceType: 'script'
}
}
],
rules: {
"@typescript-eslint/no-unused-vars": "warn",
"react/display-name": "off",
Expand Down
135 changes: 135 additions & 0 deletions packages/openinula-vchart/__tests__/base-chart-data-prop.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { act, render, unmountComponentAtNode } from 'openinula';
import { createRequire } from 'module';
import path from 'path';
import { VChart } from '../src/VChart';

const requireFromVChart = createRequire(path.resolve(__dirname, '../../vchart/package.json'));
const Canvas = requireFromVChart('canvas');

const chartOptions = {
mode: 'node' as const,
modeParams: Canvas,
animation: false
};

type Datum = { x: string; y: number };
type SeriesData = { latestData?: Datum[] };
type SeriesLike = { getRawData: () => SeriesData };
type ChartLike = {
getChart: () => { getAllSeries: () => SeriesLike[] };
release: () => void;
};

const readYValues = (chart: ChartLike) => {
const series = chart.getChart().getAllSeries();
const latestData = series[0]?.getRawData()?.latestData;
if (!Array.isArray(latestData)) {
throw new Error('bar series did not expose rendered data');
}
return latestData.map(datum => datum.y);
};

describe('openinula VChart data prop', () => {
let container: HTMLDivElement;
let chart: ChartLike | null;

const setChart = (instance: ChartLike | null) => {
if (instance) {
chart = instance;
}
};

beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
chart = null;
});

afterEach(() => {
act(() => {
unmountComponentAtNode(container);
});
container.remove();
chart = null;
});

const renderChart = async (element: React.ReactElement) => {
await act(() => {
render(element, container);
});
if (!chart) {
throw new Error('VChart did not finish its render/act lifecycle');
}
return chart;
};

it('updates data that has no matchable id', async () => {
const spec = {
type: 'bar' as const,
width: 400,
height: 300,
animation: false,
xField: 'x',
yField: 'y',
data: [{ values: [{ x: 'A', y: 1 }] }]
};

await renderChart(
<VChart ref={setChart} spec={spec} data={[{ values: [{ x: 'A', y: 10 }] }]} options={chartOptions} />
);
expect(readYValues(chart as ChartLike)).toEqual([10]);

await renderChart(
<VChart ref={setChart} spec={spec} data={[{ values: [{ x: 'A', y: 99 }] }]} options={chartOptions} />
);
expect(readYValues(chart as ChartLike)).toEqual([99]);
});

it('restores spec.data when the data override is removed', async () => {
const spec = {
type: 'bar' as const,
width: 400,
height: 300,
animation: false,
xField: 'x',
yField: 'y',
data: [{ id: 'id0', values: [{ x: 'A', y: 1 }] }]
};
const override = [{ id: 'id0', values: [{ x: 'A', y: 10 }] }];

await renderChart(<VChart ref={setChart} spec={spec} data={override} options={chartOptions} />);
expect(readYValues(chart as ChartLike)).toEqual([10]);

await renderChart(<VChart ref={setChart} spec={spec} options={chartOptions} />);
expect(readYValues(chart as ChartLike)).toEqual([1]);

act(() => {
unmountComponentAtNode(container);
});
chart = null;
await renderChart(<VChart ref={setChart} spec={spec} options={chartOptions} />);
expect(readYValues(chart as ChartLike)).toEqual([1]);
});

it('still updates data when every dataset id already exists', async () => {
const spec = {
type: 'bar' as const,
width: 400,
height: 300,
animation: false,
xField: 'x',
yField: 'y',
data: [{ id: 'id0', values: [{ x: 'A', y: 1 }] }]
};

await renderChart(
<VChart ref={setChart} spec={spec} data={[{ id: 'id0', values: [{ x: 'A', y: 10 }] }]} options={chartOptions} />
);
expect(readYValues(chart as ChartLike)).toEqual([10]);

await renderChart(
<VChart ref={setChart} spec={spec} data={[{ id: 'id0', values: [{ x: 'A', y: 99 }] }]} options={chartOptions} />
);
expect(readYValues(chart as ChartLike)).toEqual([99]);
});
});
147 changes: 147 additions & 0 deletions packages/openinula-vchart/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
const fs = require('fs');
const path = require('path');

function escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

const packageRoots = [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, '../vchart/node_modules'),
path.resolve(__dirname, '../../common/temp/node_modules')
];

function getNodeModulePackageJson(packageName) {
const relativePath = path.join(...packageName.split('/'), 'package.json');
for (let i = 0; i < packageRoots.length; i++) {
const candidate = path.resolve(packageRoots[i], relativePath);
if (fs.existsSync(candidate)) {
return candidate;
}
}
return null;
}

function mapPackageExportsToCjs(packageName) {
const packageJsonPath = getNodeModulePackageJson(packageName);
if (!packageJsonPath) {
return {};
}
const packageJson = require(packageJsonPath);
const packageRoot = path.dirname(packageJsonPath);

return Object.entries(packageJson.exports ?? {}).reduce((mappers, [subpath, target]) => {
if (subpath === '.' || !target || typeof target !== 'object' || !target.require) {
return mappers;
}

const exportPath = subpath.slice(2);
mappers[`^${escapeRegex(packageName)}\\/${escapeRegex(exportPath)}$`] = path.resolve(
packageRoot,
target.require.replace(/\.js$/, '')
);

return mappers;
}, {});
}

function resolvePackageFile(packageName, relativeFile) {
const packageJsonPath = getNodeModulePackageJson(packageName);
if (!packageJsonPath) {
return null;
}
const resolved = path.resolve(path.dirname(packageJsonPath), relativeFile);
return fs.existsSync(resolved) || fs.existsSync(`${resolved}.js`) ? resolved : null;
}

function assignMapper(mappers, pattern, target) {
if (target) {
mappers[pattern] = target;
}
}

const vrenderPackageExportMappers = {
...mapPackageExportsToCjs('@visactor/vrender'),
...mapPackageExportsToCjs('@visactor/vrender-core'),
...mapPackageExportsToCjs('@visactor/vrender-animate'),
...mapPackageExportsToCjs('@visactor/vrender-components'),
...mapPackageExportsToCjs('@visactor/vrender-kits')
};

module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testRegex: '/__tests__/.*\\.test\\.(js|ts|tsx)$',
setupFiles: ['./jest.setup.js'],
testTimeout: 60000,
globals: {
'ts-jest': {
diagnostics: false,
isolatedModules: true,
tsconfig: {
jsx: 'react',
esModuleInterop: true,
allowJs: true,
target: 'ES2019',
module: 'commonjs',
strict: false,
skipLibCheck: true,
sourceMap: true,
composite: false,
declaration: false,
declarationMap: false,
rootDir: path.resolve(__dirname, '../..')
}
}
},
moduleNameMapper: (() => {
const mappers = {
'^@visactor/vchart$': path.resolve(__dirname, '../vchart/src/index.ts'),
'^@visactor/vutils-extension$': path.resolve(__dirname, '../vutils-extension/src/index.ts'),
...vrenderPackageExportMappers
};
assignMapper(mappers, '^d3-color$', resolvePackageFile('d3-color', 'dist/d3-color.min.js'));
assignMapper(mappers, '^d3-array$', resolvePackageFile('d3-array', 'dist/d3-array.min.js'));
assignMapper(mappers, '^d3-geo$', resolvePackageFile('d3-geo', 'dist/d3-geo.min.js'));
assignMapper(mappers, '^d3-dsv$', resolvePackageFile('d3-dsv', 'dist/d3-dsv.min.js'));
assignMapper(mappers, '^d3-hexbin$', resolvePackageFile('d3-hexbin', 'build/d3-hexbin.min.js'));
assignMapper(mappers, '^d3-hierarchy$', resolvePackageFile('d3-hierarchy', 'dist/d3-hierarchy.min.js'));
assignMapper(mappers, '^@visactor/vrender$', resolvePackageFile('@visactor/vrender', 'cjs/index'));
assignMapper(mappers, '^@visactor/vrender-core$', resolvePackageFile('@visactor/vrender-core', 'cjs/index'));
assignMapper(mappers, '^@visactor/vrender-animate$', resolvePackageFile('@visactor/vrender-animate', 'cjs/index'));
assignMapper(
mappers,
'^@visactor/vrender-components$',
resolvePackageFile('@visactor/vrender-components', 'cjs/index')
);
assignMapper(mappers, '^@visactor/vrender-kits$', resolvePackageFile('@visactor/vrender-kits', 'cjs/index-node'));
assignMapper(
mappers,
'^@visactor/vrender/(.*)$',
resolvePackageFile('@visactor/vrender', 'cjs') && `${resolvePackageFile('@visactor/vrender', 'cjs')}/$1`
);
assignMapper(
mappers,
'^@visactor/vrender-core/(.*)$',
resolvePackageFile('@visactor/vrender-core', 'cjs') && `${resolvePackageFile('@visactor/vrender-core', 'cjs')}/$1`
);
assignMapper(
mappers,
'^@visactor/vrender-animate/(.*)$',
resolvePackageFile('@visactor/vrender-animate', 'cjs') &&
`${resolvePackageFile('@visactor/vrender-animate', 'cjs')}/$1`
);
assignMapper(
mappers,
'^@visactor/vrender-components/(.*)$',
resolvePackageFile('@visactor/vrender-components', 'cjs') &&
`${resolvePackageFile('@visactor/vrender-components', 'cjs')}/$1`
);
assignMapper(
mappers,
'^@visactor/vrender-kits/(.*)$',
resolvePackageFile('@visactor/vrender-kits', 'cjs') && `${resolvePackageFile('@visactor/vrender-kits', 'cjs')}/$1`
);
return mappers;
})()
};
11 changes: 11 additions & 0 deletions packages/openinula-vchart/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
global.__DEV__ = true;
global.__VERSION__ = 'test';

const originalConsoleError = console.error;
console.error = (...args) => {
const message = args.map(arg => (arg instanceof Error ? arg.stack || arg.message : String(arg))).join(' ');
if (message.includes('HTMLCanvasElement.prototype.getContext')) {
return;
}
originalConsoleError.apply(console, args);
};
3 changes: 2 additions & 1 deletion packages/openinula-vchart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"scripts": {
"compile": "tsc --noEmit",
"start": "vite ./demo",
"build": "bundle --clean"
"build": "bundle --clean",
"test": "jest"
},
"dependencies": {
"@visactor/vchart": "workspace:2.1.7",
Expand Down
2 changes: 1 addition & 1 deletion packages/openinula-vchart/src/VChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BaseChartProps, createChart } from './charts/BaseChart';
import VChartCore from '@visactor/vchart';
export { VChartCore };

export type VChartProps = Omit<BaseChartProps, 'container' | 'data' | 'width' | 'height' | 'type'>;
export type VChartProps = Omit<BaseChartProps, 'container' | 'width' | 'height' | 'type'>;

export const VChart = createChart<VChartProps>('VChart', {
vchartConstructor: VChartCore
Expand Down
Loading
Loading