Why join() does not remove empty strings from array in javascript?

This is a test case of a simple reactjs project, where, in this case, I try to test against the textContent.

container is just a div element, which is our render target. And Simple is function that returns react component.

textContent is string, but the length of the allocated memory and length of the "logical" memory differ from each other. So, first I tried to use trim(), which didn’t work and thought it was strange, and after I tried to split() and join(), which is not the best solution, but it should have worked.

it("simple test", function () {
  act(function () {
    const Simple = Template.bind({});
    render(Simple(props), container);
  });
  let received = container.textContent.split(""); // ["A", "B", "C", "", ""]
  received = received.join(""); // "ABC"
  console.log(received.length); // but length is still 5
  let expected = values[0].name; // "ABC", with length 3

  expect(received).toBe(expected); // in conclusion, test case fails
});

EDIT: reproducible example

both files are in the same directory.

// CurrencyTextField.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import CurrencyTextField from "./CurrencyTextField";

let container = null;
let Template;
let currencies;
let props;
beforeEach(function () {
  container = document.createElement("div");
  document.body.appendChild(container);

  props = {
    currencies,
    current: currencies[0],
    handleValueChange: undefined,
    handleCurrencyChange: undefined,
    style: {
      labelWidth: 50,
      classes: { formControl: "" },
    },
    error: { isError: false, errorText: "" },
  };
});

afterEach(function () {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

beforeAll(() => {
  Template = (args) => <CurrencyTextField {...args} />;
  currencies = [
    {
      name: "USD",
      flag: "",
      symbo: "$",
      value: "1.54",
      error: {
        isError: false,
        errorText: "",
      },
    },
  ];
});

it("simple test", function () {
  act(function () {
    const Simple = Template.bind({});
    render(Simple(props), container);
  });
  let received = container.textContent.trim(); // here is the problem mentioned above.
  console.log(received);
  console.log(received.length);
  let expected = currencies[0].name;
  expect(received).toBe(expected);
});
// CurrencyTextField.js

import React from "react";
import TextField from "@material-ui/core/TextField";
import InputAdornment from "@material-ui/core/InputAdornment";
import FormControl from "@material-ui/core/FormControl";
import FormHelperText from "@material-ui/core/FormHelperText";
import InputLabel from "@material-ui/core/InputLabel";
import OutlinedInput from "@material-ui/core/OutlinedInput";
import MenuItem from "@material-ui/core/MenuItem";
import PropTypes from "prop-types";

const CurrencyTextField = (props) => {
  const {
    currencies,
    current,
    handleValueChange,
    handleCurrencyChange,
    style,
  } = props;
  const { classes, labelWidth } = style;
  const { formControl } = classes;
  const { name, value } = current;
  const { errorText, isError } = current.error;

  return (
    <FormControl className={formControl} variant="outlined">
      <InputLabel htmlFor={`${name}-currency-value`}>{name}</InputLabel>
      <OutlinedInput
        id={`${name}-currency-value`}
        value={value}
        onChange={handleValueChange}
        error={isError}
        endAdornment={
          <InputAdornment position="end">
            <TextField select value={name} onChange={handleCurrencyChange}>
              {currencies.map((currency) => (
                <MenuItem key={currency.name} value={currency.name}>
                  {currency.symbol}
                </MenuItem>
              ))}
            </TextField>
          </InputAdornment>
        }
        labelWidth={labelWidth | 55}
      />
      <FormHelperText id={`${name}-helper-text`}>{errorText}</FormHelperText>
    </FormControl>
  );
};

const _c = PropTypes.shape({
  name: PropTypes.string,
  flag: PropTypes.string,
  symbol: PropTypes.string,
  value: PropTypes.string,
  error: PropTypes.shape({
    isError: PropTypes.bool,
    errorText: PropTypes.string,
  }),
});

CurrencyTextField.propTypes = {
  currencies: PropTypes.arrayOf(_c),
  current: _c,
  handleValueChange: PropTypes.func,
  handleCurrencyChange: PropTypes.func,
  style: PropTypes.object,
};

export default CurrencyTextField;

package.json:

{
  "name": "exchange",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "4.11.3",
    "@material-ui/icons": "4.11.2",
    "@storybook/cli": "6.1.18",
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "clsx": "1.1.1",
    "money": "0.2.0",
    "nice-color-palettes": "3.0.0",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.2",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "start": "BROWSER=none react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "storybook": "BROWSER=none start-storybook -p 6006 -s public",
    "build-storybook": "build-storybook -s public"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@storybook/addon-actions": "^6.1.18",
    "@storybook/addon-essentials": "^6.1.18",
    "@storybook/addon-links": "^6.1.18",
    "@storybook/node-logger": "^6.1.18",
    "@storybook/preset-create-react-app": "^3.1.6",
    "@storybook/react": "^6.1.18"
  }
}

The DOM folding happens because of the OutlinedInput component.

And yarn test fails.

5 thoughts on “Why join() does not remove empty strings from array in javascript?”

  1. There’s no such concept as

    the length of the allocated memory and length of the "logical" memory differ from each other

    in JavaScript, at least not as far as the user is concerned.

    You might be looking for container.innerText instead. I think the whitespace you’re seeing in textContent is a result of otherwise folded DOM whitespace being there, ref.

    For other node types, textContent returns the concatenation of the textContent of every child node, excluding comments and processing instructions.
    MDN

    Reply

Leave a Comment