From 17f07940a9b344e99759bdef474fdf173f3212d2 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Tue, 13 Jan 2026 16:12:58 +0100 Subject: [PATCH 1/3] Add tests for function N --- package.json | 2 +- test/unit/interpreter/function-n.spec.ts | 191 +++++++++++++++++++++++ 2 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 test/unit/interpreter/function-n.spec.ts diff --git a/package.json b/package.json index ca27ce7fc..b9d1c938e 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "test": "npm-run-all lint test:unit test:browser test:compatibility", "test:unit": "cross-env NODE_ICU_DATA=node_modules/full-icu jest", "test:watch": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --watch", - "test:tdd": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --watch adding-sheet", + "test:tdd": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --watch function-n", "test:coverage": "npm run test:unit -- --coverage", "test:logMemory": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --runInBand --logHeapUsage", "test:unit.ci": "cross-env NODE_ICU_DATA=node_modules/full-icu node --expose-gc ./node_modules/jest/bin/jest --forceExit", diff --git a/test/unit/interpreter/function-n.spec.ts b/test/unit/interpreter/function-n.spec.ts new file mode 100644 index 000000000..0f3d1688b --- /dev/null +++ b/test/unit/interpreter/function-n.spec.ts @@ -0,0 +1,191 @@ +import {HyperFormula} from '../../../src' +import {ErrorType} from '../../../src/Cell' +import {ErrorMessage} from '../../../src/error-message' +import {adr, detailedError} from '../testUtils' + +describe('Function N', () => { + describe('Argument validation', () => { + it('should take exactly one argument', () => { + const engine = HyperFormula.buildFromArray([ + ['=N()'], + ['=N(1, 2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA, ErrorMessage.WrongArgNumber)) + }) + }) + + describe('Numeric values', () => { + it('should return the number for numeric input', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(42)'], + ['=N(-42)'], + ['=N(3.14)'], + ['=N(-3.14)'], + ['=N(0)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(42) + expect(engine.getCellValue(adr('A2'))).toBe(-42) + expect(engine.getCellValue(adr('A3'))).toBe(3.14) + expect(engine.getCellValue(adr('A4'))).toBe(-3.14) + expect(engine.getCellValue(adr('A5'))).toBe(0) + }) + + it('should return number from cell reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(B1)', 123], + ['=N(B2)', -456.78], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(123) + expect(engine.getCellValue(adr('A2'))).toBe(-456.78) + }) + }) + + describe('Logical values', () => { + it('should return 1 for TRUE and 0 for FALSE', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(TRUE)'], + ['=N(FALSE)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(1) + expect(engine.getCellValue(adr('A2'))).toBe(0) + }) + + it('should return 1 for TRUE and 0 for FALSE from cell reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(B1)', '=TRUE()'], + ['=N(B2)', '=FALSE()'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(1) + expect(engine.getCellValue(adr('A2'))).toBe(0) + }) + }) + + describe('Text values', () => { + it('should return 0 for text strings', () => { + const engine = HyperFormula.buildFromArray([ + ['=N("Hello")'], + ['=N("123")'], + ['=N("")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(0) + expect(engine.getCellValue(adr('A2'))).toBe(0) + expect(engine.getCellValue(adr('A3'))).toBe(0) + }) + + it('should return 0 for boolean values as text', () => { + const engine = HyperFormula.buildFromArray([ + ['=N("TRUE")'], + ['=N("FALSE")'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(0) + expect(engine.getCellValue(adr('A2'))).toBe(0) + }) + + it('should return 0 for text from cell reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(B1)', 'Hello'], + ['=N(B2)', '123'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(0) + expect(engine.getCellValue(adr('A2'))).toBe(0) + }) + }) + + describe('Date values', () => { + it('should return serial number for date', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(DATE(2011, 4, 17))'], + ]) + + // April 17, 2011 = serial number 40650 in 1900 date system + expect(engine.getCellValue(adr('A1'))).toBe(40650) + }) + + it('should return serial number for date from cell reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(B1)', '=DATE(2011, 4, 17)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(40650) + }) + }) + + describe('Empty/Null values', () => { + it('should return 0 for empty cell reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(B1)', null], + ['=N(B2)'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(0) + expect(engine.getCellValue(adr('A2'))).toBe(0) + }) + }) + + describe('Error propagation', () => { + it('should propagate error values', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(1/0)'], + ['=N(NA())'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.DIV_BY_ZERO)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NA)) + }) + + it('should propagate error from cell reference', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(B1)', '=1/0'], + ['=N(B2)', '=FOO()'], + ]) + + expect(engine.getCellValue(adr('A1'))).toEqualError(detailedError(ErrorType.DIV_BY_ZERO)) + expect(engine.getCellValue(adr('A2'))).toEqualError(detailedError(ErrorType.NAME, ErrorMessage.FunctionName('FOO'))) + }) + }) + + describe('Range values', () => { + it('should use first cell value from range', () => { + const engine = HyperFormula.buildFromArray([ + [42], + ['text'], + [100], + ['=N(A1:A3)'], + ]) + + expect(engine.getCellValue(adr('A4'))).toBe(42) + }) + + it('should return 0 when first cell of range is text', () => { + const engine = HyperFormula.buildFromArray([ + ['text'], + [42], + [100], + ['=N(A1:A3)'], + ]) + + expect(engine.getCellValue(adr('A4'))).toBe(0) + }) + }) + + describe('Nested function calls', () => { + it('should handle nested N calls', () => { + const engine = HyperFormula.buildFromArray([ + ['=N(N("text"))'], + ['=N(N(42))'], + ]) + + expect(engine.getCellValue(adr('A1'))).toBe(0) + expect(engine.getCellValue(adr('A2'))).toBe(42) + }) + }) +}) From 349c272bb9dea5853e44e20a5deee4b7d7ffdcc9 Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Tue, 13 Jan 2026 16:30:39 +0100 Subject: [PATCH 2/3] Add function N --- CHANGELOG.md | 1 + docs/guide/built-in-functions.md | 1 + package.json | 2 +- src/i18n/languages/csCZ.ts | 1 + src/i18n/languages/daDK.ts | 1 + src/i18n/languages/deDE.ts | 1 + src/i18n/languages/enGB.ts | 1 + src/i18n/languages/esES.ts | 1 + src/i18n/languages/fiFI.ts | 1 + src/i18n/languages/frFR.ts | 1 + src/i18n/languages/huHU.ts | 1 + src/i18n/languages/itIT.ts | 1 + src/i18n/languages/nbNO.ts | 1 + src/i18n/languages/nlNL.ts | 1 + src/i18n/languages/plPL.ts | 1 + src/i18n/languages/ptPT.ts | 1 + src/i18n/languages/ruRU.ts | 1 + src/i18n/languages/svSE.ts | 1 + src/i18n/languages/trTR.ts | 1 + src/interpreter/plugin/TextPlugin.ts | 40 +++++++++++++++++++++++- test/unit/interpreter/function-n.spec.ts | 39 +++++++++++++++++++++-- 21 files changed, 94 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c8d466d5..5a3af8b55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added - Added a new function: IRR. [#1591](https://github.com/handsontable/hyperformula/issues/1591) +- Added a new function: N. [#1585](https://github.com/handsontable/hyperformula/issues/1585) ## [3.1.1] - 2025-12-18 diff --git a/docs/guide/built-in-functions.md b/docs/guide/built-in-functions.md index 7b428c1b6..0847d17ff 100644 --- a/docs/guide/built-in-functions.md +++ b/docs/guide/built-in-functions.md @@ -492,6 +492,7 @@ Total number of functions: **{{ $page.functionsCount }}** | LEN | Returns length of a given text. | LEN("Text") | | LOWER | Returns text converted to lowercase. | LOWER(Text) | | MID | Returns substring of a given length starting from Start_position. | MID(Text, Start_position, Length) | +| N | Converts a value to a number. | N(Value) | | PROPER | Capitalizes words given text string. | PROPER("Text") | | REPLACE | Replaces substring of a text of a given length that starts at given position. | REPLACE(Text, Start_position, Length, New_text) | | REPT | Repeats text a given number of times. | REPT("Text", Number) | diff --git a/package.json b/package.json index b9d1c938e..ca27ce7fc 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "test": "npm-run-all lint test:unit test:browser test:compatibility", "test:unit": "cross-env NODE_ICU_DATA=node_modules/full-icu jest", "test:watch": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --watch", - "test:tdd": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --watch function-n", + "test:tdd": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --watch adding-sheet", "test:coverage": "npm run test:unit -- --coverage", "test:logMemory": "cross-env NODE_ICU_DATA=node_modules/full-icu jest --runInBand --logHeapUsage", "test:unit.ci": "cross-env NODE_ICU_DATA=node_modules/full-icu node --expose-gc ./node_modules/jest/bin/jest --forceExit", diff --git a/src/i18n/languages/csCZ.ts b/src/i18n/languages/csCZ.ts index b5e64d348..febe6867b 100644 --- a/src/i18n/languages/csCZ.ts +++ b/src/i18n/languages/csCZ.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'SOUČIN.MATIC', MOD: 'MOD', MONTH: 'MĚSÍC', + N: 'N', NA: 'NEDEF', NETWORKDAYS: 'NETWORKDAYS', 'NETWORKDAYS.INTL': 'NETWORKDAYS.INTL', diff --git a/src/i18n/languages/daDK.ts b/src/i18n/languages/daDK.ts index dbf971561..6ffd42bac 100644 --- a/src/i18n/languages/daDK.ts +++ b/src/i18n/languages/daDK.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MPRODUKT', MOD: 'REST', MONTH: 'MÅNED', + N: 'TAL', NA: 'IKKE.TILGÆNGELIG', NETWORKDAYS: 'ANTAL.ARBEJDSDAGE', 'NETWORKDAYS.INTL': 'ANTAL.ARBEJDSDAGE.INTL', diff --git a/src/i18n/languages/deDE.ts b/src/i18n/languages/deDE.ts index 716a260c2..55ff125bb 100644 --- a/src/i18n/languages/deDE.ts +++ b/src/i18n/languages/deDE.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'REST', MONTH: 'MONAT', + N: 'N', NA: 'NV', NETWORKDAYS: 'NETTOARBEITSTAGE', 'NETWORKDAYS.INTL': 'NETTOARBEITSTAGE.INTL', diff --git a/src/i18n/languages/enGB.ts b/src/i18n/languages/enGB.ts index ff4e73bdb..6603d21af 100644 --- a/src/i18n/languages/enGB.ts +++ b/src/i18n/languages/enGB.ts @@ -150,6 +150,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'MOD', MONTH: 'MONTH', + N: 'N', NA: 'NA', NETWORKDAYS: 'NETWORKDAYS', 'NETWORKDAYS.INTL': 'NETWORKDAYS.INTL', diff --git a/src/i18n/languages/esES.ts b/src/i18n/languages/esES.ts index 3de8fe4e8..fb40a03ac 100644 --- a/src/i18n/languages/esES.ts +++ b/src/i18n/languages/esES.ts @@ -149,6 +149,7 @@ export const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'RESIDUO', MONTH: 'MES', + N: 'N', NA: 'NOD', NETWORKDAYS: 'DIAS.LAB', 'NETWORKDAYS.INTL': 'DIAS.LAB.INTL', diff --git a/src/i18n/languages/fiFI.ts b/src/i18n/languages/fiFI.ts index 3e38400f8..7b1fdb584 100644 --- a/src/i18n/languages/fiFI.ts +++ b/src/i18n/languages/fiFI.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MKERRO', MOD: 'JAKOJ', MONTH: 'KUUKAUSI', + N: 'N', NA: 'PUUTTUU', NETWORKDAYS: 'TYÖPÄIVÄT', 'NETWORKDAYS.INTL': 'TYÖPÄIVÄT.KANSVÄL', diff --git a/src/i18n/languages/frFR.ts b/src/i18n/languages/frFR.ts index c38a9b3f0..2f811ad95 100644 --- a/src/i18n/languages/frFR.ts +++ b/src/i18n/languages/frFR.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'PRODUITMAT', MOD: 'MOD', MONTH: 'MOIS', + N: 'N', NA: 'NA', NETWORKDAYS: 'NB.JOURS.OUVRES', 'NETWORKDAYS.INTL': 'NB.JOURS.OUVRES.INTL', diff --git a/src/i18n/languages/huHU.ts b/src/i18n/languages/huHU.ts index fee4ce1cb..e992f97ef 100644 --- a/src/i18n/languages/huHU.ts +++ b/src/i18n/languages/huHU.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MSZORZAT', MOD: 'MARADÉK', MONTH: 'HÓNAP', + N: 'S', NA: 'HIÁNYZIK', NETWORKDAYS: 'ÖSSZ.MUNKANAP', 'NETWORKDAYS.INTL': 'ÖSSZ.MUNKANAP.INTL', diff --git a/src/i18n/languages/itIT.ts b/src/i18n/languages/itIT.ts index 73824f318..6659d5439 100644 --- a/src/i18n/languages/itIT.ts +++ b/src/i18n/languages/itIT.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MATR.PRODOTTO', MOD: 'RESTO', MONTH: 'MESE', + N: 'NUM', NA: 'NON.DISP', NETWORKDAYS: 'GIORNI.LAVORATIVI.TOT', 'NETWORKDAYS.INTL': 'GIORNI.LAVORATIVI.TOT.INTL', diff --git a/src/i18n/languages/nbNO.ts b/src/i18n/languages/nbNO.ts index 501439b2b..729fe1042 100644 --- a/src/i18n/languages/nbNO.ts +++ b/src/i18n/languages/nbNO.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'REST', MONTH: 'MÅNED', + N: 'N', NA: 'IT', NETWORKDAYS: 'NETT.ARBEIDSDAGER', 'NETWORKDAYS.INTL': 'NETT.ARBEIDSDAGER.INTL', diff --git a/src/i18n/languages/nlNL.ts b/src/i18n/languages/nlNL.ts index 6e25af59f..0407a479f 100644 --- a/src/i18n/languages/nlNL.ts +++ b/src/i18n/languages/nlNL.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'PRODUCTMAT', MOD: 'REST', MONTH: 'MAAND', + N: 'N', NA: 'NB', NETWORKDAYS: 'NETTO.WERKDAGEN', 'NETWORKDAYS.INTL': 'NETWERKDAGEN.INTL', diff --git a/src/i18n/languages/plPL.ts b/src/i18n/languages/plPL.ts index b95ff9d38..3d6a941d5 100644 --- a/src/i18n/languages/plPL.ts +++ b/src/i18n/languages/plPL.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MACIERZ.ILOCZYN', MOD: 'MOD', MONTH: 'MIESIĄC', + N: 'N', NA: 'BRAK', NETWORKDAYS: 'DNI.ROBOCZE', 'NETWORKDAYS.INTL': 'DNI.ROBOCZE.NIESTAND', diff --git a/src/i18n/languages/ptPT.ts b/src/i18n/languages/ptPT.ts index 6a6fb701a..1679748c7 100644 --- a/src/i18n/languages/ptPT.ts +++ b/src/i18n/languages/ptPT.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MATRIZ.MULT', MOD: 'MOD', MONTH: 'MÊS', + N: 'N', NA: 'NÃO.DISP', NETWORKDAYS: 'DIATRABALHOTOTAL', 'NETWORKDAYS.INTL': 'DIATRABALHOTOTAL.INTL', diff --git a/src/i18n/languages/ruRU.ts b/src/i18n/languages/ruRU.ts index 70bafb967..69a56fd8f 100644 --- a/src/i18n/languages/ruRU.ts +++ b/src/i18n/languages/ruRU.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'МУМНОЖ', MOD: 'ОСТАТ', MONTH: 'МЕСЯЦ', + N: 'Ч', NA: 'НД', NETWORKDAYS: 'ЧИСТРАБДНИ', 'NETWORKDAYS.INTL': 'ЧИСТРАБДНИ.МЕЖД', diff --git a/src/i18n/languages/svSE.ts b/src/i18n/languages/svSE.ts index 393c9b694..ceaf0649d 100644 --- a/src/i18n/languages/svSE.ts +++ b/src/i18n/languages/svSE.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'MMULT', MOD: 'REST', MONTH: 'MÅNAD', + N: 'N', NA: 'SAKNAS', NETWORKDAYS: 'NETTOARBETSDAGAR', 'NETWORKDAYS.INTL': 'NETTOARBETSDAGAR.INT', diff --git a/src/i18n/languages/trTR.ts b/src/i18n/languages/trTR.ts index b29cb9806..3255b0a7d 100644 --- a/src/i18n/languages/trTR.ts +++ b/src/i18n/languages/trTR.ts @@ -149,6 +149,7 @@ const dictionary: RawTranslationPackage = { MMULT: 'DÇARP', MOD: 'MOD', MONTH: 'AY', + N: 'S', NA: 'YOKSAY', NETWORKDAYS: 'TAMİŞGÜNÜ', 'NETWORKDAYS.INTL': 'TAMİŞGÜNÜ.ULUSL', diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index 0c65ef44c..abeb55c2f 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -7,7 +7,8 @@ import {CellError, ErrorType} from '../../Cell' import {ErrorMessage} from '../../error-message' import {ProcedureAst} from '../../parser' import {InterpreterState} from '../InterpreterState' -import {InterpreterValue, RawScalarValue} from '../InterpreterValue' +import {InternalScalarValue, InterpreterValue, RawScalarValue} from '../InterpreterValue' +import {SimpleRangeValue} from '../../SimpleRangeValue' import {FunctionArgumentType, FunctionPlugin, FunctionPluginTypecheck, ImplementedFunctions} from './FunctionPlugin' /** @@ -69,6 +70,12 @@ export class TextPlugin extends FunctionPlugin implements FunctionPluginTypechec {argumentType: FunctionArgumentType.SCALAR} ] }, + 'N': { + method: 'n', + parameters: [ + {argumentType: FunctionArgumentType.ANY} + ] + }, 'PROPER': { method: 'proper', parameters: [ @@ -335,6 +342,37 @@ export class TextPlugin extends FunctionPlugin implements FunctionPluginTypechec }) } + /** + * Corresponds to N(value) + * + * Converts a value to a number according to Excel specification: + * - Numbers return themselves + * - Dates return their serial number (stored as numbers internally) + * - TRUE returns 1, FALSE returns 0 + * - Error values propagate + * - Anything else (text, empty) returns 0 + * - For ranges, uses the first cell value + * + * @param ast + * @param state + */ + public n(ast: ProcedureAst, state: InterpreterState): InterpreterValue { + return this.runFunction(ast.args, state, this.metadata('N'), (arg: InternalScalarValue | SimpleRangeValue) => { + const value = arg instanceof SimpleRangeValue ? arg.data[0]?.[0] : arg + + if (value instanceof CellError) { + return value + } + if (typeof value === 'number') { + return value + } + if (typeof value === 'boolean') { + return value ? 1 : 0 + } + return 0 + }) + } + public upper(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('UPPER'), (arg: string) => { return arg.toUpperCase() diff --git a/test/unit/interpreter/function-n.spec.ts b/test/unit/interpreter/function-n.spec.ts index 0f3d1688b..63b5c84ff 100644 --- a/test/unit/interpreter/function-n.spec.ts +++ b/test/unit/interpreter/function-n.spec.ts @@ -47,8 +47,8 @@ describe('Function N', () => { describe('Logical values', () => { it('should return 1 for TRUE and 0 for FALSE', () => { const engine = HyperFormula.buildFromArray([ - ['=N(TRUE)'], - ['=N(FALSE)'], + ['=N(TRUE())'], + ['=N(FALSE())'], ]) expect(engine.getCellValue(adr('A1'))).toBe(1) @@ -92,7 +92,7 @@ describe('Function N', () => { it('should return 0 for text from cell reference', () => { const engine = HyperFormula.buildFromArray([ ['=N(B1)', 'Hello'], - ['=N(B2)', '123'], + ['=N(B2)', '\'123'], ]) expect(engine.getCellValue(adr('A1'))).toBe(0) @@ -175,6 +175,39 @@ describe('Function N', () => { expect(engine.getCellValue(adr('A4'))).toBe(0) }) + + it('should propagate error from first cell of range', () => { + const engine = HyperFormula.buildFromArray([ + ['=1/0'], + [42], + [100], + ['=N(A1:A3)'], + ]) + + expect(engine.getCellValue(adr('A4'))).toEqualError(detailedError(ErrorType.DIV_BY_ZERO)) + }) + + it('should return 0 when first cell of range is empty', () => { + const engine = HyperFormula.buildFromArray([ + [null], + [42], + [100], + ['=N(A1:A3)'], + ]) + + expect(engine.getCellValue(adr('A4'))).toBe(0) + }) + + it('should return 1 when first cell of range is TRUE', () => { + const engine = HyperFormula.buildFromArray([ + ['=TRUE()'], + [42], + [100], + ['=N(A1:A3)'], + ]) + + expect(engine.getCellValue(adr('A4'))).toBe(1) + }) }) describe('Nested function calls', () => { From 87d6246ac9e88c7181e96bc268dd8559aec9e66c Mon Sep 17 00:00:00 2001 From: Kuba Sekowski Date: Tue, 13 Jan 2026 16:51:10 +0100 Subject: [PATCH 3/3] Fix linter error --- src/interpreter/plugin/TextPlugin.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/interpreter/plugin/TextPlugin.ts b/src/interpreter/plugin/TextPlugin.ts index abeb55c2f..c9802828b 100644 --- a/src/interpreter/plugin/TextPlugin.ts +++ b/src/interpreter/plugin/TextPlugin.ts @@ -352,9 +352,6 @@ export class TextPlugin extends FunctionPlugin implements FunctionPluginTypechec * - Error values propagate * - Anything else (text, empty) returns 0 * - For ranges, uses the first cell value - * - * @param ast - * @param state */ public n(ast: ProcedureAst, state: InterpreterState): InterpreterValue { return this.runFunction(ast.args, state, this.metadata('N'), (arg: InternalScalarValue | SimpleRangeValue) => {