简体   繁体   English

为什么 join() 不会从 javascript 中的数组中删除空字符串?

[英]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 .这是一个简单的reactjs项目的测试用例,在这种情况下,我尝试针对textContent进行测试。

container is just a div element, which is our render target. container只是一个div元素,它是我们的渲染目标。 And Simple is function that returns react component.Simple是返回 react 组件的函数。

textContent is string, but the length of the allocated memory and length of the "logical" memory differ from each other. textContent是字符串,但分配的内存长度和“逻辑”内存的长度不同。 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.所以,首先我尝试使用trim() ,它不起作用并认为它很奇怪,在我尝试split()join() ,这不是最好的解决方案,但它应该有效。

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 : 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. DOM 折叠的发生是因为OutlinedInput组件。

And yarn test fails.并且yarn test失败。

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.在 JavaScript 中,至少对用户而言不是。

You might be looking for container.innerText instead.您可能正在寻找container.innerText代替。 I think the whitespace you're seeing in textContent is a result of otherwise folded DOM whitespace being there, ref.我认为您在textContent看到的空白是其他折叠的 DOM 空白存在的结果,参考。

For other node types, textContent returns the concatenation of the textContent of every child node, excluding comments and processing instructions.对于其他节点类型,textContent 返回每个子节点的 textContent 的串联,不包括注释和处理指令。
MDN MDN

Ok, so here's what I found.好的,这就是我发现的。

The two strings received at the end, that I mistakenly thought were empty strings, are actually unicode characters (each with unicode \​ ) which are zero-width space characters.最后收到的两个字符串,我错误地认为是空字符串,实际上是 unicode 字符(每个都有 unicode \​ ),它们是zero-width space字符。

So, this is the reason why trim() , or join() didn't work as expected.所以,这就是为什么trim()join()没有按预期工作的原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM