Skip to content

Add TEXTJOIN function with i18n and documentation#1640

Open
marcin-kordas-hoc wants to merge 12 commits intodevelopfrom
feature/TEXTJOIN_w_docs
Open

Add TEXTJOIN function with i18n and documentation#1640
marcin-kordas-hoc wants to merge 12 commits intodevelopfrom
feature/TEXTJOIN_w_docs

Conversation

@marcin-kordas-hoc
Copy link
Collaborator

@marcin-kordas-hoc marcin-kordas-hoc commented Mar 18, 2026

Summary

Adds the TEXTJOIN function — joins text from multiple strings and/or ranges with a configurable delimiter.

Replaces #1625 (was opened from fork, now from upstream branch directly).

Features

  • Scalar and array/range delimiter support with cycling behavior
  • ignore_empty parameter to skip empty strings
  • Type coercion (numbers, booleans → strings)
  • Error propagation from both delimiter and text arguments
  • 32,767 character limit (Excel compatibility)
  • i18n translations for all 17 supported languages
  • Documentation in built-in-functions.md

Implementation

  • New textjoin method + flattenArgToStrings helper in TextPlugin
  • repeatLastArgs: 1 metadata pattern (same as SUMPRODUCT, etc.)
  • Defensive CellError check on coerceScalarToString return value

Changed files

File Change
src/interpreter/plugin/TextPlugin.ts textjoin() + flattenArgToStrings()
src/error-message.ts TextJoinResultTooLong message
src/i18n/languages/*.ts (17 files) TEXTJOIN translations
docs/guide/built-in-functions.md TEXTJOIN row (alphabetically between TEXT and TRIM)

Review feedback addressed (from #1625)

  • Tests moved to private hyperformula-tests repo (companion PR pending)
  • Fixed docs alphabetical ordering
  • Fixed unsafe as string cast in flattenArgToStrings

Test plan

  • 35 tests in hyperformula-tests/unit/interpreter/function-textjoin.spec.ts
  • Full suite: 480 suites / 5396 tests passed

Note

Medium Risk
Adds new formula evaluation logic (TEXTJOIN) including range flattening, type coercion, and new error handling; main risk is subtle compatibility/edge-case differences and potential performance impact when joining large ranges.

Overview
Adds the TEXTJOIN spreadsheet function to the interpreter (TextPlugin), supporting scalar or range delimiters (cycled between joined values), optional skipping of empty strings, and coercion of scalar values to strings with error propagation.

Introduces a new #VALUE! error message when the joined result exceeds 32,767 characters, and wires TEXTJOIN into i18n function-name dictionaries across all supported languages. Updates the built-in functions guide to document TEXTJOIN syntax and behavior.

Written by Cursor Bugbot for commit 7aca32c. This will update automatically on new commits. Configure here.

sequba and others added 10 commits February 20, 2026 13:18
* Fix package-lock file

* Docs: remove CodeSandbox embedded demos and add links to working exa,ples in Stackblitz (#1621)
Implement Excel-compatible TEXTJOIN(delimiter, ignore_empty, text1, ...)
as a native function in TextPlugin alongside CONCATENATE. The function
joins text from multiple strings/ranges with a configurable delimiter
and optionally skips empty strings. Returns #VALUE! when the result
exceeds 32,767 characters (Excel cell content limit).

- Add TEXTJOIN metadata and method to TextPlugin.ts
- Add TextJoinResultTooLong error message to error-message.ts
- Add function name translations to all 17 language files
- Add 7 test cases covering ignore_empty, ranges, edge cases

https://claude.ai/code/session_0138Q9YKLkHZ9geHUsEC6zFr
- Rewrite TEXTJOIN to accept range/array as delimiter argument with cycling behavior
  (e.g., TEXTJOIN({"-","/"}, TRUE, "a","b","c") → "a-b/c")
- Change parameter types from STRING to ANY to support ranges without expandRanges
- Manually flatten text ranges and coerce values to strings
- Add 26 dedicated unit tests in test/textjoin.spec.ts covering:
  - Basic functionality (literal strings, ranges, empty delimiter)
  - ignore_empty behavior (empty cells, ="" cells, all-empty ranges)
  - Array/range delimiter cycling
  - Type coercion (numbers, booleans)
  - Error propagation (#DIV/0!, #N/A)
  - Edge cases (32767 char limit, mixed scalar and range args)
- Add array delimiter smoke test

https://claude.ai/code/session_0138Q9YKLkHZ9geHUsEC6zFr
- Remove dead code: redundant CellError check after coerceScalarToString in flattenArgToStrings
- Remove empty no-op afterEach callback from textjoin test suite
Per code review: smoke.spec.ts should stay minimal. All TEXTJOIN tests
are now consolidated in textjoin.spec.ts, covering individual cell
references, single-ref args, and explicit empty-string vs null cell
behaviour for ignore_empty=TRUE/FALSE.
- Delete test/textjoin.spec.ts (tests moved to hyperformula-tests)
- Fix TEXTJOIN position in built-in-functions.md: move between TEXT and TRIM
- Replace `as string` cast with explicit CellError check on coerceScalarToString return value
- Remove duplicate textjoin/flattenArgToStrings methods introduced during rebase
@claude
Copy link

claude bot commented Mar 18, 2026

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit in Settings → Usage.

Once credits are available, push a new commit or reopen this pull request to trigger a review.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Template literal variable won't interpolate in markdown links
    • Replaced plain markdown links with Vue dynamic :href bindings using backtick template literals so $page.buildDateURIEncoded is properly interpolated at render time, matching the pattern used by the original iframe :src bindings.

Create PR

Or push these changes by commenting:

@cursor push bbcf633ee4
Preview (bbcf633ee4)
diff --git a/docs/guide/custom-functions.md b/docs/guide/custom-functions.md
--- a/docs/guide/custom-functions.md
+++ b/docs/guide/custom-functions.md
@@ -358,7 +358,7 @@
 
 ## Working demo
 
-Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/custom-functions?v=${$page.buildDateURIEncoded}).
+Explore the full working example on <a :href="`https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/custom-functions?v=${$page.buildDateURIEncoded}`">Stackblitz</a>.
 
 This demo contains the implementation of both the
 [`GREET`](#add-a-simple-custom-function) and

diff --git a/docs/guide/integration-with-angular.md b/docs/guide/integration-with-angular.md
--- a/docs/guide/integration-with-angular.md
+++ b/docs/guide/integration-with-angular.md
@@ -6,4 +6,4 @@
 
 ## Demo
 
-Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=${$page.buildDateURIEncoded}).
+Explore the full working example on <a :href="`https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/angular-demo?v=${$page.buildDateURIEncoded}`">Stackblitz</a>.

diff --git a/docs/guide/integration-with-react.md b/docs/guide/integration-with-react.md
--- a/docs/guide/integration-with-react.md
+++ b/docs/guide/integration-with-react.md
@@ -6,4 +6,4 @@
 
 ## Demo
 
-Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=${$page.buildDateURIEncoded}).
+Explore the full working example on <a :href="`https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/react-demo?v=${$page.buildDateURIEncoded}`">Stackblitz</a>.

diff --git a/docs/guide/integration-with-svelte.md b/docs/guide/integration-with-svelte.md
--- a/docs/guide/integration-with-svelte.md
+++ b/docs/guide/integration-with-svelte.md
@@ -6,4 +6,4 @@
 
 ## Demo
 
-Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}).
+Explore the full working example on <a :href="`https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/svelte-demo?v=${$page.buildDateURIEncoded}`">Stackblitz</a>.

diff --git a/docs/guide/integration-with-vue.md b/docs/guide/integration-with-vue.md
--- a/docs/guide/integration-with-vue.md
+++ b/docs/guide/integration-with-vue.md
@@ -31,7 +31,7 @@
 
 ## Demo
 
-Explore the full working example on [Stackblitz](https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/vue-3-demo?v=${$page.buildDateURIEncoded}).
+Explore the full working example on <a :href="`https://stackblitz.com/github/handsontable/hyperformula-demos/tree/3.2.x/vue-3-demo?v=${$page.buildDateURIEncoded}`">Stackblitz</a>.
 
 ::: tip
 This demo uses the [Vue 3](https://v3.vuejs.org/) framework. If you are looking for an example using Vue 2, check out the [code on GitHub](https://github.com/handsontable/hyperformula-demos/tree/2.5.x/vue-demo).

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

}
let result = parts[0]
for (let i = 1; i < parts.length; i++) {
result += delimiters[(i - 1) % delimiters.length] + parts[i]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No guard against empty delimiters array causes corruption

Low Severity

If delimiters is an empty array (e.g., from an unusual SimpleRangeValue with no data), the expression delimiters[(i - 1) % delimiters.length] computes a modulo by zero, producing NaN as the index. delimiters[NaN] evaluates to undefined, which gets coerced to the literal string "undefined" during concatenation, silently corrupting the result instead of returning an error.

Fix in Cursor Fix in Web

@codecov
Copy link

codecov bot commented Mar 18, 2026

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.18%. Comparing base (4042b04) to head (7aca32c).

Files with missing lines Patch % Lines
src/interpreter/plugin/TextPlugin.ts 96.96% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1640      +/-   ##
===========================================
- Coverage    97.18%   97.18%   -0.01%     
===========================================
  Files          172      172              
  Lines        14836    14870      +34     
  Branches      3258     3266       +8     
===========================================
+ Hits         14418    14451      +33     
- Misses         418      419       +1     
Files with missing lines Coverage Δ
src/error-message.ts 100.00% <100.00%> (ø)
src/i18n/languages/csCZ.ts 100.00% <ø> (ø)
src/i18n/languages/daDK.ts 100.00% <ø> (ø)
src/i18n/languages/deDE.ts 100.00% <ø> (ø)
src/i18n/languages/enGB.ts 100.00% <ø> (ø)
src/i18n/languages/esES.ts 100.00% <ø> (ø)
src/i18n/languages/fiFI.ts 100.00% <ø> (ø)
src/i18n/languages/frFR.ts 100.00% <ø> (ø)
src/i18n/languages/huHU.ts 100.00% <ø> (ø)
src/i18n/languages/itIT.ts 100.00% <ø> (ø)
... and 8 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link

github-actions bot commented Mar 18, 2026

Performance comparison of head (7aca32c) vs base (4042b04)

                                     testName |   base |   head | change
------------------------------------------------------------------------
                                      Sheet A | 485.75 |  482.2 | -0.73%
                                      Sheet B | 154.54 | 149.35 | -3.36%
                                      Sheet T | 138.14 | 132.85 | -3.83%
                                Column ranges | 470.72 | 467.74 | -0.63%
Sheet A:  change value, add/remove row/column |  15.88 |  15.09 | -4.97%
 Sheet B: change value, add/remove row/column | 130.04 | 124.81 | -4.02%
                   Column ranges - add column | 147.52 | 142.05 | -3.71%
                Column ranges - without batch | 469.83 | 437.75 | -6.83%
                        Column ranges - batch | 113.54 | 111.52 | -1.78%

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants