From 74a435448373e7620dfafb91a152dd4bf6f4cad2 Mon Sep 17 00:00:00 2001 From: shiyuhang Date: Tue, 19 Jul 2022 16:08:08 +0800 Subject: [PATCH] fix(formula): runtime error i18n support --- .../docs_legacy/pages/DocumentContentPage.tsx | 8 ++- .../__tests__/FormulaDisplay.test.tsx.snap | 2 +- .../__tests__/FormulaResult.test.tsx.snap | 2 +- .../__tests__/FormulaValue.test.tsx.snap | 6 +- .../blockViews/FormulaView/useFormula.ts | 4 +- .../Spreadsheet/SpreadsheetCell.tsx | 8 ++- .../Spreadsheet/SpreadsheetRender.tsx | 14 +++-- .../Spreadsheet/useFormulaSpreadsheet.ts | 14 ++--- .../__tests__/AutocompleteList.test.tsx | 2 +- .../components/ui/Formula/FormulaError.tsx | 9 +-- .../components/ui/Formula/FormulaValue.tsx | 8 ++- .../ui/Formula/Render/FormulaSpreadsheet.tsx | 2 +- .../Formula/__tests__/FormulaDisplay.test.tsx | 16 +++++ .../Formula/__tests__/FormulaResult.test.tsx | 16 +++++ .../Formula/__tests__/FormulaValue.test.tsx | 16 +++++ packages/editor/src/helpers/formula.ts | 13 +++- .../formula/src/__tests__/control.test.ts | 4 +- packages/formula/src/__tests__/event.test.ts | 2 +- .../formula/src/__tests__/formulas.test.ts | 4 +- .../formula/src/__tests__/grammar.test.ts | 2 +- .../formula/src/__tests__/persist.test.ts | 26 +++++++- packages/formula/src/context/context.ts | 63 +++++++++---------- packages/formula/src/context/persist.ts | 5 +- packages/formula/src/context/task.ts | 2 +- packages/formula/src/context/variable.ts | 18 +++--- packages/formula/src/controls/block.ts | 6 +- packages/formula/src/controls/cell.ts | 2 +- packages/formula/src/controls/spreadsheet.ts | 14 ++--- packages/formula/src/controls/types.ts | 1 + packages/formula/src/events/inner.ts | 6 +- packages/formula/src/grammar/codeFragment.ts | 5 +- packages/formula/src/grammar/dependency.ts | 2 +- .../formula/src/grammar/operations/access.ts | 5 +- .../src/grammar/operations/equalCompare.ts | 5 +- .../src/grammar/operations/multiplication.ts | 5 +- packages/formula/src/grammar/util.ts | 8 ++- .../tests/feature/event/spreadsheetEvent.ts | 16 ++--- .../src/tests/feature/event/variableEvent.ts | 4 +- .../formula/src/tests/feature/functionCall.ts | 17 ++++- packages/formula/src/tests/testCases.ts | 2 +- packages/formula/src/tests/testEvent.ts | 12 ++-- packages/formula/src/tests/testHelper.ts | 6 +- packages/formula/src/type/index.ts | 12 +++- packages/formula/src/types/array.ts | 5 +- packages/formula/src/types/error.ts | 3 +- packages/formula/src/types/record.ts | 4 +- 46 files changed, 266 insertions(+), 140 deletions(-) diff --git a/apps/client-web/src/docs_legacy/pages/DocumentContentPage.tsx b/apps/client-web/src/docs_legacy/pages/DocumentContentPage.tsx index 44967cf2e..72521595f 100644 --- a/apps/client-web/src/docs_legacy/pages/DocumentContentPage.tsx +++ b/apps/client-web/src/docs_legacy/pages/DocumentContentPage.tsx @@ -18,9 +18,12 @@ import { useFormulaActions } from './hooks/useFormulaActions' import { AppError404 } from '@/routes/_shared/AppError' import { type DocMeta, DocMetaProvider } from '../store/DocMeta' import { MashcardEventBus, BlockMetaUpdated, ReloadDocument } from '@mashcard/schema' +import { formulaI18n } from '@mashcard/editor/src/helpers' +import { useFormulaI18n } from '@mashcard/editor/src/hooks/useFormulaI18n' export const DocumentContentPage: FC = () => { const { t } = useDocsI18n() + const { t: formulaT } = useFormulaI18n() const { domain, docId, historyId } = useParams() as unknown as { domain: string docId?: string @@ -141,8 +144,9 @@ export const DocumentContentPage: FC = () => { useEffect(() => { const functionClauses = generateFormulaFunctionClauses() - const formulaContext = FormulaContext.getInstance({ - domain, + const formulaContext = FormulaContext.getFormulaInstance({ + username: domain, + i18n: formulaI18n(formulaT), backendActions: { commit: commitFormula }, functionClauses, features: featureFlags diff --git a/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaDisplay.test.tsx.snap b/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaDisplay.test.tsx.snap index 5682a6191..d2bc58b6e 100644 --- a/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaDisplay.test.tsx.snap +++ b/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaDisplay.test.tsx.snap @@ -759,7 +759,7 @@ exports[`FormulaDisplay [partial error 2] basic "=custom::ADD(1)" 2`] = ` class="mashcard-formula-borderless" style="color: rgb(207, 31, 40);" > - #<Error> errors.parse.not_found.function,[object Object] + #<Error> errors.parse.not_found.function {"key":"custom::ADD"} diff --git a/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaResult.test.tsx.snap b/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaResult.test.tsx.snap index d1b36f1da..383551870 100644 --- a/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaResult.test.tsx.snap +++ b/packages/editor/__snapshots__/components/ui/Formula/__tests__/FormulaResult.test.tsx.snap @@ -2410,7 +2410,7 @@ exports[`FormulaResult chain "={a:1}.b" -> [object Object] 1`] = ` class="mashcard-formula-borderless" style="color: rgb(207, 31, 40);" > - #<Error> errors.interpret.not_found.key,[object Object] + #<Error> errors.interpret.not_found.key {"key":"b"} - #<Error> errors.parse.not_found.function,[object Object] + #<Error> errors.parse.not_found.function {"key":"custom::ADD"} `; @@ -1063,7 +1063,7 @@ exports[`FormulaValue [partial error 2] basic "=custom::ADD(1)" 3`] = ` class="mashcard-formula-borderless" style="color: rgb(207, 31, 40);" > - #<Error> errors.parse.not_found.function,[object Object] + #<Error> errors.parse.not_found.function {"key":"custom::ADD"} `; @@ -1074,7 +1074,7 @@ exports[`FormulaValue [partial error 2] basic "=custom::ADD(1)" 4`] = ` class="mashcard-formula-borderless" style="color: rgb(207, 31, 40);" > - #<Error> errors.parse.not_found.function,[object Object] + #<Error> errors.parse.not_found.function {"key":"custom::ADD"} `; diff --git a/packages/editor/src/components/blockViews/FormulaView/useFormula.ts b/packages/editor/src/components/blockViews/FormulaView/useFormula.ts index ac546c397..e36e59a4b 100644 --- a/packages/editor/src/components/blockViews/FormulaView/useFormula.ts +++ b/packages/editor/src/components/blockViews/FormulaView/useFormula.ts @@ -505,12 +505,12 @@ export const useFormula = ({ setReferences(e.payload.meta) }, { - eventId: `${formulaContext?.domain}#${namespaceId},${variableId}`, + eventId: `${formulaContext?.username}#${namespaceId},${variableId}`, subscribeId: `UseFormula#${namespaceId},${variableId}` } ) return () => listener.unsubscribe() - }, [formulaContext?.domain, namespaceId, variableId]) + }, [formulaContext?.username, namespaceId, variableId]) React.useEffect(() => { const listener = MashcardEventBus.subscribe( diff --git a/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetCell.tsx b/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetCell.tsx index 5489255d7..186b0e802 100644 --- a/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetCell.tsx +++ b/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetCell.tsx @@ -79,7 +79,7 @@ export const SpreadsheetCell: React.FC = ({ async (variable: VariableInterface | undefined): Promise => { if (!variable) return // TODO check no persist - const value = display(fetchResult(variable.t)).result + const value = display(fetchResult(variable.t), formulaContext!).result const oldValue = block.text if (value === oldValue) return devLog('Spreadsheet cell formula updated', { cellId, value, rootId }) @@ -98,7 +98,7 @@ export const SpreadsheetCell: React.FC = ({ }, meta: null, namespaceId: rootId, - username: formulaContext.domain, + username: formulaContext.username, key: variable?.currentUUID ?? tableId }) ) @@ -194,7 +194,9 @@ export const SpreadsheetCell: React.FC = ({ latestEditing.current = false } - const displayResult = savedVariableT ? display(fetchResult(savedVariableT)).result : currentBlock.text + const displayResult = savedVariableT + ? display(fetchResult(savedVariableT), formulaContext!).result + : currentBlock.text const fallbackDisplayData: VariableDisplayData | undefined = displayResult ? { definition: displayResult, diff --git a/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetRender.tsx b/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetRender.tsx index 8c1bfb4ed..0ef9fdf08 100644 --- a/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetRender.tsx +++ b/packages/editor/src/components/blockViews/Spreadsheet/SpreadsheetRender.tsx @@ -12,7 +12,7 @@ import { SpreadsheetCellContainer, SpreadsheetColumnEditable } from './SpreadsheetView' -import { display, VariableDisplayData } from '@mashcard/formula' +import { display, SpreadsheetType, VariableDisplayData } from '@mashcard/formula' import React from 'react' import { FormulaDisplay } from '../../ui/Formula' import { styled } from '@mashcard/design-system' @@ -29,7 +29,7 @@ export interface Column { } export interface SpreadsheetRenderProps { - title: string + spreadsheet: SpreadsheetType rows: Row[] valuesMatrix: Map> columns: Column[] @@ -38,7 +38,7 @@ export interface SpreadsheetRenderProps { export const SpreadsheetRender: React.FC = ({ rows, - title, + spreadsheet, columns, valuesMatrix, defaultSelection @@ -56,7 +56,7 @@ export const SpreadsheetRender: React.FC = ({ new Map( columns.map(c => { const displayData = valuesMatrix.get(rowId)?.get(c.columnId) - return [c.columnId, displayData ? display(displayData.result).result : ''] + return [c.columnId, displayData ? display(displayData.result, spreadsheet._formulaContext).result : ''] }) ) ] @@ -99,7 +99,8 @@ export const SpreadsheetRender: React.FC = ({ context={spreadsheetContext} columnId={column.columnId} columnActions={[]} - draggable={false}> + draggable={false} + > = ({ + cellId={{ rowId: rowBlock.rowId, columnId: column.columnId }} + >
{ // TODO: remove this when switch pages @@ -75,18 +75,18 @@ export function useFormulaSpreadsheet({ spreadsheetId, namespaceId: rootId, rows: rowData, - username: formulaContext?.domain + username: formulaContext?.username }) - }, [rootId, spreadsheetId, rowData, formulaContext?.domain]) + }, [rootId, spreadsheetId, rowData, formulaContext?.username]) React.useEffect(() => { void dispatchFormulaSpreadsheetColumnChange({ spreadsheetId, namespaceId: rootId, columns: columnData, - username: formulaContext?.domain + username: formulaContext?.username }) - }, [rootId, spreadsheetId, columnData, formulaContext?.domain]) + }, [rootId, spreadsheetId, columnData, formulaContext?.username]) React.useEffect(() => { if (!formulaContext) return @@ -123,7 +123,7 @@ export function useFormulaSpreadsheet({ return { deleteSpreadsheet: () => { - void dispatchFormulaSpreadsheetRemove({ id: spreadsheetId, username: formulaContext?.domain }) + void dispatchFormulaSpreadsheetRemove({ id: spreadsheetId, username: formulaContext?.username }) } } } diff --git a/packages/editor/src/components/ui/Formula/AutocompleteList/__tests__/AutocompleteList.test.tsx b/packages/editor/src/components/ui/Formula/AutocompleteList/__tests__/AutocompleteList.test.tsx index a9e35f3dd..7aa91825d 100644 --- a/packages/editor/src/components/ui/Formula/AutocompleteList/__tests__/AutocompleteList.test.tsx +++ b/packages/editor/src/components/ui/Formula/AutocompleteList/__tests__/AutocompleteList.test.tsx @@ -208,7 +208,7 @@ describe('AutocompleteList', () => { }) it('renders variable kind correctly', async () => { - const formulaContext = FormulaContext.getInstance({ domain: 'test' }) + const formulaContext = FormulaContext.getFormulaInstance({ username: 'test' }) const interpretContext = { ctx: {}, arguments: [] } const namespaceId = '37198be0-d10d-42dc-ae8b-20d45a95401b' diff --git a/packages/editor/src/components/ui/Formula/FormulaError.tsx b/packages/editor/src/components/ui/Formula/FormulaError.tsx index 0f551d1fa..6dd6fe9d8 100644 --- a/packages/editor/src/components/ui/Formula/FormulaError.tsx +++ b/packages/editor/src/components/ui/Formula/FormulaError.tsx @@ -1,5 +1,6 @@ import { FC } from 'react' import { ErrorMessage } from '@mashcard/formula' +import { formulaI18n } from '../../../helpers' import { useFormulaI18n } from '../../../hooks/useFormulaI18n' export interface FormulaErrorProps { @@ -8,13 +9,7 @@ export interface FormulaErrorProps { export const FormulaError: FC = ({ error }) => { const { t } = useFormulaI18n() - - let errorMessage: string - if (typeof error.message === 'string') { - errorMessage = t(error.message) - } else { - errorMessage = t(error.message[0], error.message[1]) - } + const errorMessage = formulaI18n(t)(error.message) return ( <> diff --git a/packages/editor/src/components/ui/Formula/FormulaValue.tsx b/packages/editor/src/components/ui/Formula/FormulaValue.tsx index 2fe5f0f71..9c3ac1d7e 100644 --- a/packages/editor/src/components/ui/Formula/FormulaValue.tsx +++ b/packages/editor/src/components/ui/Formula/FormulaValue.tsx @@ -1,9 +1,10 @@ import { FC, ReactElement } from 'react' import { resultToColorType, VariableDisplayData, display } from '@mashcard/formula' import { cx, Icon, Tooltip } from '@mashcard/design-system' -import { SelectedType } from '../../blockViews/FormulaView' +import { getFormulaContext, SelectedType } from '../../blockViews/FormulaView' import { FORMULA_COLOR_METAS, FORMULA_ICONS, FORMULA_STYLES } from './color' import * as Root from './Formula.style' +import { useEditorContext } from '../../../hooks' export interface FormulaValueProps { displayData: VariableDisplayData @@ -20,11 +21,14 @@ export const FormulaValue: FC = ({ disablePopover, displayData: { result, type } }) => { + const { editor } = useEditorContext() + const formulaContext = getFormulaContext(editor) + const colorType = resultToColorType(result) const { colorCode } = FORMULA_COLOR_METAS[colorType] const icon = FORMULA_ICONS[colorType] const hasBorder = type === 'normal' && border - const displayResult = display(result).result + const displayResult = display(result, formulaContext!).result if (!hasBorder) { return ( diff --git a/packages/editor/src/components/ui/Formula/Render/FormulaSpreadsheet.tsx b/packages/editor/src/components/ui/Formula/Render/FormulaSpreadsheet.tsx index 6f5b69c4c..47aacbc1e 100644 --- a/packages/editor/src/components/ui/Formula/Render/FormulaSpreadsheet.tsx +++ b/packages/editor/src/components/ui/Formula/Render/FormulaSpreadsheet.tsx @@ -38,7 +38,7 @@ export const FormulaSpreadsheet: React.FC = ({ return ( { + const { useEditorContext } = jest.requireActual('../../../../hooks/useEditorContext') + return { useEditorContext: jest.fn().mockImplementation(useEditorContext) } +}) + describe('FormulaDisplay', () => { let ctx: Awaited> beforeAll(async () => { jest.useRealTimers() ctx = await makeContext(input.options) jest.clearAllTimers() + + const editor = mockEditor({ + extensionManager: { + extensions: [{ name: Formula.name, options: { formulaContext: ctx.formulaContext } }] + } + }) + + jest.spyOn(editorHooks, 'useEditorContext').mockImplementation(() => ({ editor, documentEditable: true })) }) it.each(input.basicTestCases)('$jestTitle', async args => { diff --git a/packages/editor/src/components/ui/Formula/__tests__/FormulaResult.test.tsx b/packages/editor/src/components/ui/Formula/__tests__/FormulaResult.test.tsx index 2261081c8..7ca2db2ec 100644 --- a/packages/editor/src/components/ui/Formula/__tests__/FormulaResult.test.tsx +++ b/packages/editor/src/components/ui/Formula/__tests__/FormulaResult.test.tsx @@ -1,6 +1,9 @@ import { BasicNames, buildTestCases, makeContext, TestCaseInput } from '@mashcard/formula' import { render } from '@testing-library/react' +import { Formula } from '../../../../extensions' +import { mockEditor } from '../../../../test' import { FormulaResult } from '../FormulaResult' +import * as editorHooks from '../../../../hooks/useEditorContext' const [input, testCases] = buildTestCases(BasicNames) @@ -17,12 +20,25 @@ jest.mock('react-i18next', () => ({ } })) +jest.mock('../../../../hooks/useEditorContext', () => { + const { useEditorContext } = jest.requireActual('../../../../hooks/useEditorContext') + return { useEditorContext: jest.fn().mockImplementation(useEditorContext) } +}) + describe('FormulaResult', () => { let ctx: Awaited> beforeAll(async () => { jest.useRealTimers() ctx = await makeContext(input.options) jest.clearAllTimers() + + const editor = mockEditor({ + extensionManager: { + extensions: [{ name: Formula.name, options: { formulaContext: ctx.formulaContext } }] + } + }) + + jest.spyOn(editorHooks, 'useEditorContext').mockImplementation(() => ({ editor, documentEditable: true })) }) it.each(testCases)('$jestTitle', async args => { diff --git a/packages/editor/src/components/ui/Formula/__tests__/FormulaValue.test.tsx b/packages/editor/src/components/ui/Formula/__tests__/FormulaValue.test.tsx index 75610adb2..de2764d90 100644 --- a/packages/editor/src/components/ui/Formula/__tests__/FormulaValue.test.tsx +++ b/packages/editor/src/components/ui/Formula/__tests__/FormulaValue.test.tsx @@ -1,15 +1,31 @@ import { render } from '@testing-library/react' import { buildTestCases, dumpDisplayResultForDisplay, makeContext } from '@mashcard/formula' import { FormulaValue } from '../FormulaValue' +import { mockEditor } from '../../../../test' +import { Formula } from '../../../../extensions' +import * as editorHooks from '../../../../hooks/useEditorContext' const [input] = buildTestCases(['basic']) +jest.mock('../../../../hooks/useEditorContext', () => { + const { useEditorContext } = jest.requireActual('../../../../hooks/useEditorContext') + return { useEditorContext: jest.fn().mockImplementation(useEditorContext) } +}) + describe('FormulaValue', () => { let ctx: Awaited> beforeAll(async () => { jest.useRealTimers() ctx = await makeContext(input.options) jest.clearAllTimers() + + const editor = mockEditor({ + extensionManager: { + extensions: [{ name: Formula.name, options: { formulaContext: ctx.formulaContext } }] + } + }) + + jest.spyOn(editorHooks, 'useEditorContext').mockImplementation(() => ({ editor, documentEditable: true })) }) it.each(input.basicTestCases)('$jestTitle', async args => { diff --git a/packages/editor/src/helpers/formula.ts b/packages/editor/src/helpers/formula.ts index b6dd618f3..3846b85a4 100644 --- a/packages/editor/src/helpers/formula.ts +++ b/packages/editor/src/helpers/formula.ts @@ -1,5 +1,6 @@ -import { CodeFragmentWithIndex } from '@mashcard/formula' +import { CodeFragmentWithIndex, ErrorMessageType, I18N } from '@mashcard/formula' import { JSONContent } from '@tiptap/core' +import { TFunction } from 'react-i18next' import { FormulaInput } from '../components/blockViews' const maybeRemoveDefinitionEqual = (definition: string | undefined, formulaIsNormal: boolean): string => { @@ -84,3 +85,13 @@ export const codeFragments2content = ( const contents = newCodeFragments.map(codeFragment => attrsToJSONContent(codeFragment)) return [buildJSONContentByArray(contents), newCodeFragments] } + +export const formulaI18n = + (t: TFunction): I18N => + (message: ErrorMessageType) => { + if (typeof message === 'string') { + return t(message) + } else { + return t(message[0], message[1]) + } + } diff --git a/packages/formula/src/__tests__/control.test.ts b/packages/formula/src/__tests__/control.test.ts index 1e1c07f77..84be06700 100644 --- a/packages/formula/src/__tests__/control.test.ts +++ b/packages/formula/src/__tests__/control.test.ts @@ -15,12 +15,12 @@ const page: PageInput = { describe('Controls', () => { it('feature', async () => { - const noFeatureCtx = await makeContext({ initializeOptions: { domain: 'test', features: [] }, pages: [page] }) + const noFeatureCtx = await makeContext({ initializeOptions: { username: 'test', features: [] }, pages: [page] }) const input = `=Button("Foo", Set(#${namespaceId}.${testName1}, (1 + #${namespaceId}.${testName1})))` const { errorMessages: errorMessage1 } = noFeatureCtx.parseDirectly({ definition: input, namespaceId }) expect(errorMessage1).toEqual([{ message: ['errors.parse.not_found.function', { key: 'Button' }], type: 'deps' }]) - const featureCtx = await makeContext({ initializeOptions: { domain: 'test' }, pages: [page] }) + const featureCtx = await makeContext({ initializeOptions: { username: 'test' }, pages: [page] }) featureCtx.formulaContext.features = [] const { errorMessages: errorMessage2 } = featureCtx.parseDirectly({ definition: input, namespaceId }) expect(errorMessage2).toEqual([{ message: 'Feature formula-controls not enabled', type: 'deps' }]) diff --git a/packages/formula/src/__tests__/event.test.ts b/packages/formula/src/__tests__/event.test.ts index e019201ae..4b0899b49 100644 --- a/packages/formula/src/__tests__/event.test.ts +++ b/packages/formula/src/__tests__/event.test.ts @@ -13,7 +13,7 @@ describe('event', () => { ctx = await makeContext({ ...input.options, uuidFunction: index => uuid(), - initializeOptions: { domain: uuid() } + initializeOptions: { username: uuid() } }) }) diff --git a/packages/formula/src/__tests__/formulas.test.ts b/packages/formula/src/__tests__/formulas.test.ts index 558b9d58e..370dbd827 100644 --- a/packages/formula/src/__tests__/formulas.test.ts +++ b/packages/formula/src/__tests__/formulas.test.ts @@ -4,7 +4,7 @@ import { FormulaContext } from '../context/context' describe('appendFormulas TODO', () => { it('constant', async () => { - const formulaContext = new FormulaContext({ domain: 'test' }) + const formulaContext = new FormulaContext({ username: 'test' }) await appendFormulas(formulaContext, []) expect(formulaContext.variables).toEqual({}) @@ -40,7 +40,7 @@ describe('appendFormulas TODO', () => { }) it('parse error', async () => { - const formulaContext = new FormulaContext({ domain: 'test' }) + const formulaContext = new FormulaContext({ username: 'test' }) await appendFormulas(formulaContext, []) expect(formulaContext.variables).toEqual({}) diff --git a/packages/formula/src/__tests__/grammar.test.ts b/packages/formula/src/__tests__/grammar.test.ts index 25c0d9b93..7dd4ae106 100644 --- a/packages/formula/src/__tests__/grammar.test.ts +++ b/packages/formula/src/__tests__/grammar.test.ts @@ -140,7 +140,7 @@ describe('Simple test case TODO', () => { if (value !== undefined) { const variableValue = await innerInterpret({ parseResult, ctx: { ...ctx, meta: newMeta } }) - const displayResult = displayF(variableValue.result) + const displayResult = displayF(variableValue.result, ctx.formulaContext) expect(errorMessages).toEqual([]) diff --git a/packages/formula/src/__tests__/persist.test.ts b/packages/formula/src/__tests__/persist.test.ts index 471f32fce..552875a89 100644 --- a/packages/formula/src/__tests__/persist.test.ts +++ b/packages/formula/src/__tests__/persist.test.ts @@ -176,7 +176,7 @@ const cells: Cell[] = [ } ] -const formulaContext = FormulaContext.getInstance({ domain: 'test' }) +const formulaContext = FormulaContext.getFormulaInstance({ username: 'test' }) void dispatchFormulaBlockNameChange({ id: namespaceId, name: 'Page1', username: 'test' }) const spreadsheet: SpreadsheetType = new SpreadsheetClass({ @@ -351,6 +351,28 @@ const testCases: { testCase: { type: 'Error', result: { message: 'bang!', type: 'runtime' } }, displayResult: { type: 'Error', result: '# bang!' }, dumpResult: { type: 'Error', result: { message: 'bang!', type: 'runtime' } } + }, + { + testCase: { + type: 'Error', + result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } + }, + displayResult: { type: 'Error', result: '# errors.interpret.runtime.division_by_zero' }, + dumpResult: { + type: 'Error', + result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } + } + }, + { + testCase: { + type: 'Error', + result: { message: ['errors.interpret.not_found.key', { key: 'foo' }], type: 'runtime' } + }, + displayResult: { type: 'Error', result: '# errors.interpret.not_found.key {"key":"foo"}' }, + dumpResult: { + type: 'Error', + result: { message: ['errors.interpret.not_found.key', { key: 'foo' }], type: 'runtime' } + } } ], Array: [ @@ -452,7 +474,7 @@ describe('persist', () => { expect(castedValue).toMatchObject(castedResult) } - const displayValue = display(testCase) + const displayValue = display(testCase, formulaContext) if (displayResult !== null) { // eslint-disable-next-line jest/no-conditional-expect expect(displayValue).toEqual(displayResult) diff --git a/packages/formula/src/context/context.ts b/packages/formula/src/context/context.ts index 3e4cbb936..fea20d10f 100644 --- a/packages/formula/src/context/context.ts +++ b/packages/formula/src/context/context.ts @@ -11,6 +11,7 @@ import { SpreadsheetReloadViaId } from '../events' import { buildFunctionKey, BUILTIN_CLAUSES } from '../functions' +import { defaultI18n } from '../grammar' import { CodeFragmentVisitor } from '../grammar/codeFragment' import { block2completion, @@ -45,6 +46,7 @@ import { FunctionGroup, FunctionKey, FunctionNameType, + I18N, NameDependencyWithKind, NamespaceId, SpreadsheetCompletion, @@ -63,7 +65,8 @@ import { FORMULA_FEATURE_CONTROL } from './features' import { dumpDisplayResultForDisplay } from './persist' export interface FormulaContextArgs { - domain: string + username: string + i18n?: I18N tickTimeout?: number functionClauses?: AnyFunctionClause[] backendActions?: BackendActions @@ -74,9 +77,10 @@ export type ContextState = any export class FormulaContext implements ContextInterface { private static instance?: FormulaContext - domain: string + options: FormulaContextArgs + username: string + i18n: I18N tickKey: string - tickTimeout: number features: Features dirtyFormulas: Record = {} variables: Record = {} @@ -90,25 +94,20 @@ export class FormulaContext implements ContextInterface { reverseVariableDependencies: Record = {} reverseFunctionDependencies: Record = {} functionClausesMap: Record = {} - backendActions: BackendActions | undefined reservedNames: string[] = [] eventListeners: EventSubscribed[] = [] variableNameStore: typeof variableNameStore = variableNameStore - constructor({ - domain, - tickTimeout, - functionClauses = [], - backendActions, - features = [FORMULA_FEATURE_CONTROL] - }: FormulaContextArgs) { - this.domain = domain - this.tickTimeout = tickTimeout ?? 1000 - this.tickKey = `FormulaContext#${domain}` + constructor(options: FormulaContextArgs) { + this.options = options + + const { username, i18n, functionClauses = [], features = [FORMULA_FEATURE_CONTROL] } = options + + this.username = username + this.tickKey = `FormulaContext#${username}` this.features = features - if (backendActions) { - this.backendActions = backendActions - } + + this.i18n = i18n ?? defaultI18n this.viewRenders = DEFAULT_VIEWS.reduce((o: Record, acc: View) => { o[acc.type] = acc.render @@ -120,7 +119,7 @@ export class FormulaContext implements ContextInterface { async e => { await this.setBlock(e.payload.id, e.payload.meta) }, - { subscribeId: `Domain#${this.domain}`, eventId: this.domain } + { subscribeId: `Domain#${this.username}`, eventId: this.username } ) this.eventListeners.push(blockNameSubscription) @@ -130,10 +129,7 @@ export class FormulaContext implements ContextInterface { async e => { await this.tick(e.payload.state) }, - { - eventId: this.tickKey, - subscribeId: `Domain#${this.domain}` - } + { eventId: this.tickKey, subscribeId: `Domain#${this.username}` } ) this.eventListeners.push(tickSubscription) @@ -180,7 +176,10 @@ export class FormulaContext implements ContextInterface { public async invoke(name: string, ctx: FunctionContext, ...args: any[]): Promise { const clause = this.functionClausesMap[name] if (!clause) { - return { type: 'Error', result: { message: ['errors.parse.not_found.function', { key: name }], type: 'fatal' } } + return { + type: 'Error', + result: { message: ['errors.parse.not_found.function', { key: name }], type: 'fatal' } + } } return await (clause.reference as (ctx: FunctionContext, ...args: any[]) => Promise)(ctx, ...args) @@ -277,7 +276,7 @@ export class FormulaContext implements ContextInterface { id: nameDependency.id, namespaceId: nameDependency.namespaceId, key: nameDependency.id, - username: this.domain, + username: this.username, scope: null, meta: { name: nameDependency.name, @@ -299,7 +298,7 @@ export class FormulaContext implements ContextInterface { id: oldName.id, namespaceId: oldName.namespaceId, key: oldName.id, - username: this.domain, + username: this.username, scope: null, meta: { name: oldName.name, @@ -341,7 +340,7 @@ export class FormulaContext implements ContextInterface { SpreadsheetReloadViaId({ id: spreadsheet.spreadsheetId, namespaceId: spreadsheet.namespaceId, - username: this.domain, + username: this.username, scope: null, meta: null, key: spreadsheet.spreadsheetId @@ -435,12 +434,12 @@ export class FormulaContext implements ContextInterface { private async tick(state: ContextState): Promise { await this.commitDirty() const newState = state - await new Promise(resolve => setTimeout(resolve, this.tickTimeout)) - MashcardEventBus.dispatch(FormulaContextTickTrigger({ domain: this.domain, state: newState })) + await new Promise(resolve => setTimeout(resolve, this.options.tickTimeout)) + MashcardEventBus.dispatch(FormulaContextTickTrigger({ username: this.username, state: newState })) } private async commitDirty(): Promise { - if (!this.backendActions) { + if (!this.options.backendActions) { this.dirtyFormulas = {} return } @@ -458,8 +457,8 @@ export class FormulaContext implements ContextInterface { } }) if (commitFormulas.length > 0 || deleteFormulas.length > 0) { - // console.log('commit dirty', commitFormulas, deleteFormulas, this.backendActions) - const { success } = await this.backendActions.commit(commitFormulas, deleteFormulas) + // console.log('commit dirty', commitFormulas, deleteFormulas, this.options.backendActions) + const { success } = await this.options.backendActions.commit(commitFormulas, deleteFormulas) if (success) { this.dirtyFormulas = {} } else { @@ -492,7 +491,7 @@ export class FormulaContext implements ContextInterface { return codeFragments.map((c, index) => ({ ...c, index })) } - public static getInstance(args: FormulaContextArgs): FormulaContext { + public static getFormulaInstance(args: FormulaContextArgs): FormulaContext { if (this.instance === undefined) { this.instance = new FormulaContext(args) } diff --git a/packages/formula/src/context/persist.ts b/packages/formula/src/context/persist.ts index 962521af4..b98b81520 100644 --- a/packages/formula/src/context/persist.ts +++ b/packages/formula/src/context/persist.ts @@ -42,8 +42,9 @@ export const cast = , } export const display = , Display extends AnyDisplayResult>( - v: Value + v: Value, + ctx: ContextInterface ): Display => { - const result: any = FormulaAttributes[v.type].display(v as any, display) + const result: any = FormulaAttributes[v.type].display(v as any, ctx, display) return result } diff --git a/packages/formula/src/context/task.ts b/packages/formula/src/context/task.ts index 314793ec8..3e4794eaf 100644 --- a/packages/formula/src/context/task.ts +++ b/packages/formula/src/context/task.ts @@ -48,7 +48,7 @@ export const createVariableTask = ({ void variableValue.then(async value => { const newTask: VariableTask = { ...task, variableValue: value, execEndTime: new Date(), async: false } const result = MashcardEventBus.dispatch( - FormulaTaskCompleted({ task: newTask, namespaceId, variableId, username: formulaContext.domain }) + FormulaTaskCompleted({ task: newTask, namespaceId, variableId, username: formulaContext.username }) ) await Promise.all(result) }) diff --git a/packages/formula/src/context/variable.ts b/packages/formula/src/context/variable.ts index 69a7063b4..89bee5979 100644 --- a/packages/formula/src/context/variable.ts +++ b/packages/formula/src/context/variable.ts @@ -137,7 +137,7 @@ export class VariableClass implements VariableInterface { await this.completeTask(e.payload) }, { - eventId: `${formulaContext.domain}#${t.meta.namespaceId},${t.meta.variableId}`, + eventId: `${formulaContext.username}#${t.meta.namespaceId},${t.meta.variableId}`, subscribeId: `Task#${t.meta.namespaceId},${t.meta.variableId}` } ) @@ -150,7 +150,7 @@ export class VariableClass implements VariableInterface { meta: this, scope: null, key: this.id, - username: this.formulaContext.domain, + username: this.formulaContext.username, level, namespaceId: this.t.meta.namespaceId, id: this.t.meta.variableId @@ -220,7 +220,7 @@ export class VariableClass implements VariableInterface { meta: newVariableDependencies, scope: null, key: this.id, - username: this.formulaContext.domain, + username: this.formulaContext.username, level: 0, namespaceId: dependency.namespaceId, id: dependency.variableId @@ -279,7 +279,7 @@ export class VariableClass implements VariableInterface { meta: newVariableDependencies, scope: null, key: this.id, - username: this.formulaContext.domain, + username: this.formulaContext.username, level: 0, namespaceId: dependency.namespaceId, id: dependency.variableId @@ -482,7 +482,7 @@ export class VariableClass implements VariableInterface { > = { kind: 'BlockRename', event: FormulaBlockNameChangedTrigger, - eventId: `${this.formulaContext.domain}#${blockId}`, + eventId: `${this.formulaContext.username}#${blockId}`, scope: {}, key: `BlockRename#${blockId}`, definitionHandler: (deps, variable, payload) => { @@ -499,7 +499,7 @@ export class VariableClass implements VariableInterface { > = { kind: 'BlockDelete', event: FormulaBlockNameDeletedTrigger, - eventId: `${this.formulaContext.domain}#${blockId}`, + eventId: `${this.formulaContext.username}#${blockId}`, scope: {}, key: `BlockDelete#${blockId}` } @@ -513,7 +513,7 @@ export class VariableClass implements VariableInterface { > = { kind: 'NameChange', event: FormulaContextNameChanged, - eventId: `${this.formulaContext.domain}#${namespaceId}#${name}`, + eventId: `${this.formulaContext.username}#${namespaceId}#${name}`, scope: {}, key: `OtherNameChange#${namespaceId}#${name}`, skipIf: (variable, payload) => variable.isReadyT @@ -525,7 +525,7 @@ export class VariableClass implements VariableInterface { > = { kind: 'NameChange', event: FormulaContextNameChanged, - eventId: `${this.formulaContext.domain}#$Block#${name}`, + eventId: `${this.formulaContext.username}#$Block#${name}`, scope: {}, key: `BlockNameChange#${namespaceId}#${name}`, skipIf: (variable, payload) => variable.isReadyT @@ -538,7 +538,7 @@ export class VariableClass implements VariableInterface { > = { kind: 'NameRemove', event: FormulaContextNameRemove, - eventId: `${this.formulaContext.domain}#${namespaceId}#${name}`, + eventId: `${this.formulaContext.username}#${namespaceId}#${name}`, scope: {}, key: `OtherNameRemove#${namespaceId}#${name}`, skipIf: (variable, payload) => !variable.isReadyT diff --git a/packages/formula/src/controls/block.ts b/packages/formula/src/controls/block.ts index d33ccc3ff..7955b2f2d 100644 --- a/packages/formula/src/controls/block.ts +++ b/packages/formula/src/controls/block.ts @@ -41,7 +41,7 @@ export class BlockClass implements BlockType { this._name = e.payload.meta await this._formulaContext.setName(this.nameDependency()) }, - { subscribeId: `Block#${this.id}`, eventId: `${this._formulaContext.domain}#${this.id}` } + { subscribeId: `Block#${this.id}`, eventId: `${this._formulaContext.username}#${this.id}` } ) const blockDeleteSubcription = MashcardEventBus.subscribe( @@ -49,7 +49,7 @@ export class BlockClass implements BlockType { async e => { await this._formulaContext.removeBlock(this.id) }, - { subscribeId: `Block#${this.id}`, eventId: `${this._formulaContext.domain}#${this.id}` } + { subscribeId: `Block#${this.id}`, eventId: `${this._formulaContext.username}#${this.id}` } ) this.eventListeners.push(blockNameSubscription, blockDeleteSubcription) @@ -86,7 +86,7 @@ export class BlockClass implements BlockType { id: this.id, namespaceId: this.id, key: this.id, - username: this._formulaContext.domain, + username: this._formulaContext.username, scope: null, meta: this._name }) diff --git a/packages/formula/src/controls/cell.ts b/packages/formula/src/controls/cell.ts index 72aa4e981..56941e437 100644 --- a/packages/formula/src/controls/cell.ts +++ b/packages/formula/src/controls/cell.ts @@ -52,7 +52,7 @@ export class CellClass implements CellType { getValue(): string { const displayData = this.spreadsheet.findCellDisplayData({ rowId: this.rowId, columnId: this.columnId }) if (displayData) { - return display(displayData.result).result + return display(displayData.result, this.spreadsheet._formulaContext).result } return this.value } diff --git a/packages/formula/src/controls/spreadsheet.ts b/packages/formula/src/controls/spreadsheet.ts index e3bd16109..a025a5090 100644 --- a/packages/formula/src/controls/spreadsheet.ts +++ b/packages/formula/src/controls/spreadsheet.ts @@ -111,7 +111,7 @@ export class SpreadsheetClass implements SpreadsheetType { await this._formulaContext.setName(this.nameDependency()) }, { - eventId: `${this._formulaContext.domain}#${namespaceId},${spreadsheetId}`, + eventId: `${this._formulaContext.username}#${namespaceId},${spreadsheetId}`, subscribeId: `Spreadsheet#${spreadsheetId}` } ) @@ -122,7 +122,7 @@ export class SpreadsheetClass implements SpreadsheetType { async e => { await this._formulaContext.removeSpreadsheet(spreadsheetId) }, - { eventId: `${this._formulaContext.domain}#${spreadsheetId}`, subscribeId: `Spreadsheet#${spreadsheetId}` } + { eventId: `${this._formulaContext.username}#${spreadsheetId}`, subscribeId: `Spreadsheet#${spreadsheetId}` } ) this.eventListeners.push(spreadsheetDeleteSubcription) @@ -144,7 +144,7 @@ export class SpreadsheetClass implements SpreadsheetType { const result = MashcardEventBus.dispatch( SpreadsheetReloadViaId({ id: this.spreadsheetId, - username: this._formulaContext.domain, + username: this._formulaContext.username, scope: { columns: changedColumnIds }, namespaceId: this.namespaceId, key: this.spreadsheetId, @@ -154,7 +154,7 @@ export class SpreadsheetClass implements SpreadsheetType { await Promise.all(result) }, { - eventId: `${this._formulaContext.domain}#${namespaceId},${spreadsheetId}`, + eventId: `${this._formulaContext.username}#${namespaceId},${spreadsheetId}`, subscribeId: `Spreadsheet#${spreadsheetId}` } ) @@ -179,7 +179,7 @@ export class SpreadsheetClass implements SpreadsheetType { const result = MashcardEventBus.dispatch( SpreadsheetReloadViaId({ id: this.spreadsheetId, - username: this._formulaContext.domain, + username: this._formulaContext.username, scope: { rows: changedRowIds }, namespaceId: this.namespaceId, key: this.spreadsheetId, @@ -189,7 +189,7 @@ export class SpreadsheetClass implements SpreadsheetType { await Promise.all(result) }, { - eventId: `${this._formulaContext.domain}#${namespaceId},${spreadsheetId}`, + eventId: `${this._formulaContext.username}#${namespaceId},${spreadsheetId}`, subscribeId: `Spreadsheet#${spreadsheetId}` } ) @@ -450,7 +450,7 @@ export class SpreadsheetClass implements SpreadsheetType { } const displayData = this._formulaContext.findVariableDisplayDataById(this.namespaceId, cell.variableId) - if (displayData) return display(displayData.result).result + if (displayData) return display(displayData.result, this._formulaContext).result return cell.value } diff --git a/packages/formula/src/controls/types.ts b/packages/formula/src/controls/types.ts index a7eadde9f..77729eded 100644 --- a/packages/formula/src/controls/types.ts +++ b/packages/formula/src/controls/types.ts @@ -190,6 +190,7 @@ export interface SpreadsheetAllPersistence { } export interface SpreadsheetType { + _formulaContext: ContextInterface spreadsheetId: SpreadsheetId namespaceId: NamespaceId namespaceName: (pageId: NamespaceId) => string diff --git a/packages/formula/src/events/inner.ts b/packages/formula/src/events/inner.ts index 1925554ec..17d1f502c 100644 --- a/packages/formula/src/events/inner.ts +++ b/packages/formula/src/events/inner.ts @@ -23,10 +23,10 @@ export const FormulaContextNameRemove = event>()( +export const FormulaContextTickTrigger = event<{ username: string; state: ContextState }, Promise>()( 'FormulaContextTickTrigger', - ({ domain, state }) => { - return { id: `FormulaContext#${domain}` } + ({ username, state }) => { + return { id: `FormulaContext#${username}` } } ) diff --git a/packages/formula/src/grammar/codeFragment.ts b/packages/formula/src/grammar/codeFragment.ts index feb6c7c0a..0b8eb1947 100644 --- a/packages/formula/src/grammar/codeFragment.ts +++ b/packages/formula/src/grammar/codeFragment.ts @@ -873,7 +873,10 @@ export class CodeFragmentVisitor extends CodeFragmentCstVisitor { const clauseErrorMessages: ErrorMessage[] = [] if (!clause) { - clauseErrorMessages.push({ message: ['errors.parse.not_found.function', { key: functionKey }], type: 'deps' }) + clauseErrorMessages.push({ + message: ['errors.parse.not_found.function', { key: functionKey }], + type: 'deps' + }) } else if (clause.feature && !this.ctx.formulaContext.features.includes(clause.feature)) { clauseErrorMessages.push({ message: `Feature ${clause.feature} not enabled`, type: 'deps' }) } diff --git a/packages/formula/src/grammar/dependency.ts b/packages/formula/src/grammar/dependency.ts index 55df5f587..b32bb7221 100644 --- a/packages/formula/src/grammar/dependency.ts +++ b/packages/formula/src/grammar/dependency.ts @@ -42,7 +42,7 @@ export const parseTrackSpreadsheet = (visitor: Visitor, spreadsheet: Spreadsheet const spreadsheetNameEventDependency: EventDependency< typeof SpreadsheetUpdateNameViaId extends EventType ? X : never > = { - eventId: `${visitor.ctx.formulaContext.domain}#${spreadsheet.namespaceId},${spreadsheet.spreadsheetId}`, + eventId: `${visitor.ctx.formulaContext.username}#${spreadsheet.namespaceId},${spreadsheet.spreadsheetId}`, event: SpreadsheetUpdateNameViaId, kind: 'SpreadsheetName', key: `SpreadsheetName#${spreadsheet.spreadsheetId}`, diff --git a/packages/formula/src/grammar/operations/access.ts b/packages/formula/src/grammar/operations/access.ts index fe3303f03..c0f0cf9d3 100644 --- a/packages/formula/src/grammar/operations/access.ts +++ b/packages/formula/src/grammar/operations/access.ts @@ -27,7 +27,10 @@ export const accessAttribute = async ( if (value) { return value } else { - return { type: 'Error', result: { message: ['errors.interpret.not_found.key', { key }], type: 'runtime' } } + return { + type: 'Error', + result: { message: ['errors.interpret.not_found.key', { key }], type: 'runtime' } + } } } diff --git a/packages/formula/src/grammar/operations/equalCompare.ts b/packages/formula/src/grammar/operations/equalCompare.ts index a48adaf99..11f4e267b 100644 --- a/packages/formula/src/grammar/operations/equalCompare.ts +++ b/packages/formula/src/grammar/operations/equalCompare.ts @@ -34,7 +34,10 @@ export const equalCompareOperator: OperatorType = { { definition: '= 1 <> "a"', result: true }, { definition: '= 4 > 3 = true', result: true }, { definition: '= 2 * 2 > 3 != false', result: true }, - { definition: '= 3 = 1/0', result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } } + { + definition: '= 3 = 1/0', + result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } + } ], errorTestCases: [ { diff --git a/packages/formula/src/grammar/operations/multiplication.ts b/packages/formula/src/grammar/operations/multiplication.ts index 8d994599e..dfbf15b16 100644 --- a/packages/formula/src/grammar/operations/multiplication.ts +++ b/packages/formula/src/grammar/operations/multiplication.ts @@ -18,7 +18,10 @@ export const multiplicationOperator: OperatorType = { result = lhsResult * rhsResult } else if (tokenMatcher(operator, Div)) { if (rhsResult === 0) { - return { type: 'Error', result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } } + return { + type: 'Error', + result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } + } } result = lhsResult / rhsResult diff --git a/packages/formula/src/grammar/util.ts b/packages/formula/src/grammar/util.ts index f785b3e5e..84c9d0077 100644 --- a/packages/formula/src/grammar/util.ts +++ b/packages/formula/src/grammar/util.ts @@ -11,7 +11,8 @@ import { FormulaCheckType, FormulaColorType, FormulaType, - FunctionContext + FunctionContext, + I18N } from '../type' import { InterpretArgument } from './interpreter' import { checkValidName } from './lexer' @@ -369,3 +370,8 @@ export const parseErrorMessage = (message: string): ErrorMessageType => { const [msg, context] = JSON.parse(message.substring(PARSE_PREFIX_FLAG.length)) return [msg, context] } + +export const defaultI18n: I18N = input => { + if (typeof input === 'string') return input + return `${input[0]} ${JSON.stringify(input[1])}` +} diff --git a/packages/formula/src/tests/feature/event/spreadsheetEvent.ts b/packages/formula/src/tests/feature/event/spreadsheetEvent.ts index 97ad5ebda..b315e35f4 100644 --- a/packages/formula/src/tests/feature/event/spreadsheetEvent.ts +++ b/packages/formula/src/tests/feature/event/spreadsheetEvent.ts @@ -153,12 +153,12 @@ export const SpreadsheetEventTestCase: TestCaseInterface = { triggerEvents: ctx => [ { event: FormulaSpreadsheetDeleted, - eventId: `${ctx.formulaContext.domain}#${spreadsheet1Id}`, - payload: { id: spreadsheet1Id, username: ctx.formulaContext.domain } + eventId: `${ctx.formulaContext.username}#${spreadsheet1Id}`, + payload: { id: spreadsheet1Id, username: ctx.formulaContext.username } }, { event: FormulaContextNameRemove, - eventId: `${ctx.formulaContext.domain}#${page0Id}#spreadsheet1foobar` + eventId: `${ctx.formulaContext.username}#${page0Id}#spreadsheet1foobar` }, { event: FormulaUpdatedViaId, @@ -166,7 +166,7 @@ export const SpreadsheetEventTestCase: TestCaseInterface = { }, { event: FormulaContextNameChanged, - eventId: `${ctx.formulaContext.domain}#${ctx.meta.namespaceId}#${ctx.meta.name}`, + eventId: `${ctx.formulaContext.username}#${ctx.meta.namespaceId}#${ctx.meta.name}`, callLength: 0 } ], @@ -179,12 +179,12 @@ export const SpreadsheetEventTestCase: TestCaseInterface = { triggerEvents: ctx => [ { event: FormulaSpreadsheetDeleted, - eventId: `${ctx.formulaContext.domain}#${spreadsheet1Id}`, - payload: { id: spreadsheet1Id, username: ctx.formulaContext.domain } + eventId: `${ctx.formulaContext.username}#${spreadsheet1Id}`, + payload: { id: spreadsheet1Id, username: ctx.formulaContext.username } }, { event: FormulaContextNameRemove, - eventId: `${ctx.formulaContext.domain}#${page0Id}#spreadsheet1foobar` + eventId: `${ctx.formulaContext.username}#${page0Id}#spreadsheet1foobar` }, { event: FormulaUpdatedViaId, @@ -192,7 +192,7 @@ export const SpreadsheetEventTestCase: TestCaseInterface = { }, { event: FormulaContextNameChanged, - eventId: `${ctx.formulaContext.domain}#${ctx.meta.namespaceId}#${ctx.meta.name}`, + eventId: `${ctx.formulaContext.username}#${ctx.meta.namespaceId}#${ctx.meta.name}`, callLength: 0 } ], diff --git a/packages/formula/src/tests/feature/event/variableEvent.ts b/packages/formula/src/tests/feature/event/variableEvent.ts index 81e790912..6937afff4 100644 --- a/packages/formula/src/tests/feature/event/variableEvent.ts +++ b/packages/formula/src/tests/feature/event/variableEvent.ts @@ -65,14 +65,14 @@ export const VariableEventTestCase: TestCaseInterface = { saveEvents: ctx => [ { event: FormulaVariableDependencyUpdated, - eventId: `${ctx.formulaContext.domain}#${page0Id},${variableId}`, + eventId: `${ctx.formulaContext.username}#${page0Id},${variableId}`, payload: { meta: [{ namespaceId: page0Id, variableId: dependencyVariableId }], id: variableId } } ], triggerEvents: ctx => [ { event: FormulaVariableDependencyUpdated, - eventId: `${ctx.formulaContext.domain}#${page0Id},${variableId}`, + eventId: `${ctx.formulaContext.username}#${page0Id},${variableId}`, payload: { meta: [], id: variableId } } ] diff --git a/packages/formula/src/tests/feature/functionCall.ts b/packages/formula/src/tests/feature/functionCall.ts index 1e45dbda1..ad55c94c8 100644 --- a/packages/formula/src/tests/feature/functionCall.ts +++ b/packages/formula/src/tests/feature/functionCall.ts @@ -46,7 +46,10 @@ export const FunctionCallTestCase: TestCaseInterface = { testCases: { functionClauses, successTestCases: [ - { definition: '= ABS(1/0)', result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } }, + { + definition: '= ABS(1/0)', + result: { message: 'errors.interpret.runtime.division_by_zero', type: 'runtime' } + }, { definition: '=Abs(-1) + abs(1) + ABS(1) + core::ABS(-1)', result: 4, @@ -183,8 +186,16 @@ export const FunctionCallTestCase: TestCaseInterface = { errorMessage: 'errors.parse.missing.closing_parenthesis', groupOptions: [{ name: 'basicError' }] }, - { definition: '=POWER(1,', errorType: 'syntax', errorMessage: 'errors.parse.missing.closing_parenthesis' }, - { definition: '=POWER(1,2', errorType: 'syntax', errorMessage: 'errors.parse.missing.closing_parenthesis' }, + { + definition: '=POWER(1,', + errorType: 'syntax', + errorMessage: 'errors.parse.missing.closing_parenthesis' + }, + { + definition: '=POWER(1,2', + errorType: 'syntax', + errorMessage: 'errors.parse.missing.closing_parenthesis' + }, { definition: '=custom::FORTY_TWO(1, 1, 1)', errorType: 'deps', diff --git a/packages/formula/src/tests/testCases.ts b/packages/formula/src/tests/testCases.ts index e653423e3..569dc2353 100644 --- a/packages/formula/src/tests/testCases.ts +++ b/packages/formula/src/tests/testCases.ts @@ -120,7 +120,7 @@ const reduceTestCaseInput = (testCases: TestCaseInterface[]): TestCaseInput => { ] }), { - options: { pages: [{ pageName: 'Default' }], initializeOptions: { domain: 'test' } }, + options: { pages: [{ pageName: 'Default' }], initializeOptions: { username: 'test' } }, successTestCases: [], completeTestCases: [], formatTestCases: [], diff --git a/packages/formula/src/tests/testEvent.ts b/packages/formula/src/tests/testEvent.ts index 5789b63f3..fd0dca998 100644 --- a/packages/formula/src/tests/testEvent.ts +++ b/packages/formula/src/tests/testEvent.ts @@ -36,17 +36,17 @@ export const AllowEvents = { empty_sync: (ctx: ExtendedCtx, args: any) => {}, empty_async: async (ctx: ExtendedCtx, args: any) => {}, blockChangeName: async (ctx: ExtendedCtx, args: OmitUsername) => - await dispatchFormulaBlockNameChange({ ...args, username: ctx.formulaContext.domain }), + await dispatchFormulaBlockNameChange({ ...args, username: ctx.formulaContext.username }), blockDelete: async (ctx: ExtendedCtx, args: OmitUsername) => - await dispatchFormulaBlockSoftDelete({ ...args, username: ctx.formulaContext.domain }), + await dispatchFormulaBlockSoftDelete({ ...args, username: ctx.formulaContext.username }), spreadsheetChangeName: async (ctx: ExtendedCtx, args: OmitUsername) => - await dispatchFormulaSpreadsheetNameChange({ ...args, username: ctx.formulaContext.domain }), + await dispatchFormulaSpreadsheetNameChange({ ...args, username: ctx.formulaContext.username }), spreadsheetDelete: async (ctx: ExtendedCtx, args: OmitUsername) => - await dispatchFormulaSpreadsheetRemove({ ...args, username: ctx.formulaContext.domain }), + await dispatchFormulaSpreadsheetRemove({ ...args, username: ctx.formulaContext.username }), columnChange: async (ctx: ExtendedCtx, args: OmitUsername) => - await dispatchFormulaSpreadsheetColumnChange({ ...args, username: ctx.formulaContext.domain }), + await dispatchFormulaSpreadsheetColumnChange({ ...args, username: ctx.formulaContext.username }), rowChange: async (ctx: ExtendedCtx, args: OmitUsername) => - await dispatchFormulaSpreadsheetRowChange({ ...args, username: ctx.formulaContext.domain }), + await dispatchFormulaSpreadsheetRowChange({ ...args, username: ctx.formulaContext.username }), variableInsertOnly: async (ctx: ExtendedCtx, args: Parameters[1]) => await variableInsertOnlyEvent(ctx, args), variableInsertAndAwait: async (ctx: ExtendedCtx, args: Parameters[1]) => diff --git a/packages/formula/src/tests/testHelper.ts b/packages/formula/src/tests/testHelper.ts index 63aa172c0..1a86dc038 100644 --- a/packages/formula/src/tests/testHelper.ts +++ b/packages/formula/src/tests/testHelper.ts @@ -167,7 +167,7 @@ const buildSpreadsheet = ( ] } -const defaultInitializeOptions: FormulaContextArgs = { domain: 'test' } +const defaultInitializeOptions: FormulaContextArgs = { username: 'test' } export const makeContext = async (options: MakeContextOptions): Promise => { let uuidState: UUIDState = { uuidFunction: options.uuidFunction ?? DEFAULT_UUID_FUNCTION, counter: 0, cache: {} } @@ -183,7 +183,7 @@ export const makeContext = async (options: MakeContextOptions): Promise [...Array(20)].map(() => uuid()) export const buildEvent = (input: Args): ((ctx: ExtendedCtx) => Promise) => { return async ctx => { for (const [f, args] of input) { - await AllowEvents[f](ctx, { ...args, username: ctx.formulaContext.domain } as any) + await AllowEvents[f](ctx, { ...args, username: ctx.formulaContext.username } as any) } } } diff --git a/packages/formula/src/type/index.ts b/packages/formula/src/type/index.ts index 957a81304..609fa0118 100644 --- a/packages/formula/src/type/index.ts +++ b/packages/formula/src/type/index.ts @@ -145,7 +145,11 @@ export interface FormulaTypeAttributes< type: Type shortName: ShortName dump: (result: Value, dumpF: (o: AnyTypeResult) => any) => Dump - display: (result: Value, displayF: (o: AnyTypeResult) => Display) => Display + display: ( + result: Value, + ctx: ContextInterface, + displayF: (o: AnyTypeResult, ctx: ContextInterface) => Display + ) => Display cast: ( dump: Dump, ctx: ContextInterface, @@ -363,7 +367,8 @@ export interface FindKey { } export interface ContextInterface { - domain: string + username: string + i18n: I18N features: string[] dirtyFormulas: Record checkName: (name: string, namespaceId: NamespaceId, variableId: VariableId) => ErrorMessage | undefined @@ -371,7 +376,6 @@ export interface ContextInterface { reverseVariableDependencies: Record reverseFunctionDependencies: Record invoke: (name: FunctionNameType, ctx: FunctionContext, ...args: any[]) => Promise - backendActions: BackendActions | undefined variableCount: () => number getDefaultVariableName: (namespaceId: NamespaceId, type: FormulaType) => string completions: (namespaceId: NamespaceId, variableId: VariableId | undefined) => Completion[] @@ -783,3 +787,5 @@ export interface ErrorMessage { readonly message: ErrorMessageType readonly type: ErrorType } + +export type I18N = (input: ErrorMessageType) => string diff --git a/packages/formula/src/types/array.ts b/packages/formula/src/types/array.ts index 54ce490ea..e0eef6744 100644 --- a/packages/formula/src/types/array.ts +++ b/packages/formula/src/types/array.ts @@ -14,5 +14,8 @@ export const FormulaArrayAttributes: FormulaTypeAttributes f(a, ctx)) return { ...rest, result: array, meta: extractSubType(array) } }, - display: ({ result, meta, ...rest }, f) => ({ ...rest, result: `[${result.map(v => f(v).result).join('')}]` }) + display: ({ result, meta, ...rest }, ctx, f) => ({ + ...rest, + result: `[${result.map(v => f(v, ctx).result).join('')}]` + }) } diff --git a/packages/formula/src/types/error.ts b/packages/formula/src/types/error.ts index b20986325..8292ee576 100644 --- a/packages/formula/src/types/error.ts +++ b/packages/formula/src/types/error.ts @@ -23,6 +23,5 @@ export const FormulaErrorAttributes: FormulaTypeAttributes rest, cast: rest => rest, - // TODO add i18n to display - display: ({ result, ...rest }) => ({ ...rest, result: `# ${result.message}` }) + display: ({ result, ...rest }, ctx) => ({ ...rest, result: `# ${ctx.i18n(result.message)}` }) } diff --git a/packages/formula/src/types/record.ts b/packages/formula/src/types/record.ts index 186ef6030..c86f92c5c 100644 --- a/packages/formula/src/types/record.ts +++ b/packages/formula/src/types/record.ts @@ -20,8 +20,8 @@ export const FormulaRecordAttributes: FormulaTypeAttributes f(a, ctx)) return { ...rest, result: record, meta: extractSubType(Object.values(record)) } }, - display: ({ result, meta, ...rest }, f) => { - const recordArray = Object.entries(result).map(([key, value]) => `${key}: ${f(value).result}`) + display: ({ result, meta, ...rest }, ctx, f) => { + const recordArray = Object.entries(result).map(([key, value]) => `${key}: ${f(value, ctx).result}`) const recordResult = recordArray.join(', ') return { ...rest, result: `{${recordResult}}` } }