ํด๋น ์๋ฌ ํด๊ฒฐ ๋ฐฉ์์ ๋ค์์ ์ค์ ์ ๊ธฐ์ค์ผ๋ก ๋ค๋ฃน๋๋ค.
package manger
pnpm: "@9.0.0"
engines
node: ">=20"
dependencies
โ "classnames": "^2.5.1"
โ "next": "^14"
โ "react": "^18.3"
โ "react-dom": "^18.3"
dev dependencies
โ "@jest/globals": "^29.7.0"
โ "@testing-library/jest-dom": "^6.4.8"
โ "@testing-library/react": "^16.0.0"
โ "@types/jest": "^29.5.12"
โ "@types/node": "^20"
โ "@types/react": "^18.3.3"
โ "@types/react-dom": "^18.3.0"
โ "eslint": "^8"
โ "eslint-config-next": "15.0.0-rc.0"
โ "jest": "^29.7.0"
โ "jest-environment-jsdom": "^29.7.0"
โ "jest-scss-transform": "^1.0.3"
โ "node-sass": "^9.0.0"
โ"sass": "^1.77.6"
โ"ts-jest": "^29.2.3"
ํด๋ ๊ตฌ์ฑ
๐ apps
ใด ๐ storybook
ใด ๐ web
๐ packages
ใด๐design-system
๐ turbo.json
๐ package.json
... ์๋ต...
๐ฉ๐ป๐ง ๋ฌธ์ ์ํฉ
https://turbo.build/repo/docs/guides/tools/jest์ ๋ฌธ์๋๋ก jest ์ค์ ์ค ์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
Jest | Turborepo
Learn how to use Jest in a Turborepo.
turbo.build
ts ํ์ผ๋ค์ ํ ์คํธ๊ฐ ์ ๋๋ฏ๋ก ๋ฌธ์ ๋ tsx, ์ฆ ์ปดํฌ๋ํธ๋ฅผ ๋ค๋ฃจ๋ ํ ์คํธ ํ์ผ๋ค์ ์์๋ค.
์ธ๋ถ์์ importํด์จ ์ปดํฌ๋ํธ๋ค ๋ชจ๋ render ํ ์ ์๋ ์ํฉ.
๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ ํด๋น ์๋ฌ ๋ฉ์์ง๋ก ๊ตฌ๊ธ๋ง์ ์ด์ฌํ ํ์ง๋ง ํ์ฌ ๋์ ํ๋ก์ ํธ์ ๋ง๋ ํด๊ฒฐ์ฑ
์ด ์์๋ค.https://stackoverflow.com/questions/51994111/jest-encountered-an-unexpected-tokenhttps://imygnam.tistory.com/80
โ๏ธ babel์ด ์๋ ts-jest ์ฌ์ฉ ์ค : babel์ ํธ๋์คํ์ผ ๋๊ตฌ์ด๋ฏ๋ก ๋ฐ๋ก ์ค์ ์ ํด์ฃผ์ง ์์ผ๋ฉด ํ์ ์ฒดํฌ๋ฅผ ํด์ฃผ์ง ์๊ธฐ ๋๋ฌธ์ ๊ฐ๊ฒฐ์ฑ์ ์ํด ts-jest ์ ํ
๐ ์ํฉ ํ์
๋จผ์ ์ง๊ธ๊น์ง ์ค์ ํด ์คฌ๋ ๊ฑฐ๋ฅผ ์ดํด๋ณด์.
์ค์
๐ web
๐ jest.config.json
{
"preset": "ts-jest",
"testEnvironment": "jsdom",
"testMatch": ["**/__tests__/**/*.ts?(x)", "**/?(*.)+(test).ts?(x)"],
"transform": {
"^.+\\.ts?$": "ts-jest",
"^.+\\.tsx?$": "ts-jest",
"^.+.test.tsx?": "ts-jest",
"^.+\\.scss$": "jest-scss-transform"
},
"moduleDirectories": ["node_modules"]
}
๐ . eslintrc.mjs
/** @type {import("eslint").Linter.Config} */
export default {
root: true,
extends: ["@tripie/eslint-config/next.js"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
overrides: [
{
files: ["/components/*/__tests__/**/*"],
env: {
jest: true,
},
},
],
};
๐ tsconfig.json
{
"extends": "@tripie/typescript-config/react-library.json",
"jsdom": "react",
"compilerOptions": {
"plugins": [
{
"name": "next"
}
],
"outDir": "dist",
"jsx": "preserve"
},
"include": [
"next-env.d.ts",
"next.config.mjs",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": ["node_modules", "dist"]
}
ํ ์คํธ ์ฝ๋
ใด ๐ components/__test__/themeButton.test.tsx
import { expect } from "@jest/globals";
import { cleanup, render } from "@testing-library/react";
import "@testing-library/jest-dom";
import "@testing-library/jest-dom/jest-globals";
import { Text } from "@tripie/design-system";
afterEach(cleanup);
it("text has 'os control'", () => {
const { getByText } = render(<Text>os control</Text>);
expect(getByText("os control")).toBeInTheDocument();
});
Text Component : @tripie/design-system/src/typography/text/_test.tsx
"use client";
import classNames from "classnames/bind";
import Style from "./_text.module.scss";
const style = classNames.bind(Style);
export interface TextProps {
dim?: boolean;
size?:
| "default"
| "h0"
| "h1"
| "h2"
| "h3"
| "h4"
| "text"
| "small"
| "tiny";
color?: "primary" | "secondary" | "danger" | "warning" | "gray" | "emphasize";
bold?: boolean;
children: string;
className?: string;
}
function Text({ children, className, ...props }: TextProps) {
const splitText = `${children}`.split("\n").map((sentence, index) => {
return (
<span className={style(className, "text")} key={index + sentence}>
{sentence}
</span>
);
});
return <div>{splitText}</div>;
}
export default Text;
๐ design-system
๋๋จธ์ง ์ค์ ์ ๋์ผํ๋ฐ ๋ค๋ฅธ ๊ฑด
๐ tsconfig.json
{
"extends": "@tripie/typescript-config/react-library.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["typings", "src"],
"exclude": ["node_modules", "dist"],
"jsx": "react-jsx"
}
์ฌ๊ธฐ์ ํํธ๋ฅผ ์ป์ด์ "jsx" ์ค์ ์ ๋ณ๊ฒฝํด ๋ดค๋ค.
๐ ํด๊ฒฐ
jsx:"preserve"์์ jsx: "react-jsxdev"๋ก ๋ณ๊ฒฝํด ์คฌ๋ค.
{
"extends": "@tripie/typescript-config/react-library.json",
"jsdom": "react",
"compilerOptions": {
"plugins": [
{
"name": "next"
}
],
"outDir": "dist",
"jsx": "react-jsxdev" // ๋ณ๊ฒฝ
},
"include": [
"next-env.d.ts",
"next.config.mjs",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": ["node_modules", "dist"]
}
๐ค ํด๊ฒฐ์ ๋๋๋ฐ, ์ ๋ ๊ฑฐ์ง?
ts ๊ณต์ ๋ฌธ์์์ jsx ์ค์ ์ ๋ฐ๋ผ ์ด๋ป๊ฒ ๋ฌ๋ผ์ง๋์ง ์ดํด๋ณด์.
๊ณต์ ๋ฌธ์์ ์ํ๋ฉด JSX๊ฐ ์ด๋ป๊ฒ ๋ณ๊ฒฝ๋ ์ง๋ฅผ ์ง์ ํ ์ ์๋ ์ค์ ์ด๋ผ๊ณ ํ๋ค.
์ฆ,. tsx ํ์ฅ์๋ก ๋ ํ์ผ๋ค์ js๋ก ์ด๋ป๊ฒ ๋ณ๊ฒฝํด ์ค ์ง์ ๋ํ ์ค์ ์ด๋ค. ๊ฐ๋ฅํ ์ค์ ์ ๋ค์ ํ๋ก ์ ๋ฆฌํ๋ค.
export const HelloWorld = () => <h1>Hello world</h1>; ๋ผ๋ ์ฝ๋๊ฐ ์๋ค๊ณ ํ๋ฉด
"react-jsx" | .js ํ์ผ๋ค์ _jsx ํจ์๋ฅผ ํธ์ถํ์ฌ js ํ์ผ๋ก ๋ฐฉ์ถ. PROD ํ๊ฒฝ์ ์ต์ ํ | import { jsx as _jsx } from "react/jsx-runtime";
export const HelloWorld = () => _jsx("h1", { children: "Hello world" });
|
"react-jsxdev" | .js ํ์ผ๋ค์ _jsx ํจ์๋ฅผ ํธ์ถํ์ฌ js ํ์ผ๋ก ๋ฐฉ์ถํ๋ค๋ ์ ์ react-jsx์ ๋์ผํ์ง๋ง, DEV ํ๊ฒฝ์์๋ง |
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
const _jsxFileName = "/home/runner/work/TypeScript-Website/TypeScript-Website/packages/typescriptlang-org/index.tsx";
export const HelloWorld = () => _jsxDEV("h1", { children: "Hello world" }, void 0, false, { fileName: _jsxFileName, lineNumber: 9, columnNumber: 32 }, this);
|
"preserve" | .jsx ํ์ผ๋ค์ jsx ๋ณ๊ฒฝ์์ด ๋ฐฉ์ถ |
import React from 'react';
export const HelloWorld = () => <h1>Hello world</h1>;
|
"react-native" | .jsx ํ์ผ๋ค์ jsx ๋ณ๊ฒฝ์์ด ๋ฐฉ์ถ |
import React from 'react';
export const HelloWorld = () => <h1>Hello world</h1>;
|
"react" | .js ํ์ผ๋ค์ jsx React.createElement ํธ์ถ๊ณผ ๋๋ฑํ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝํ์ฌ ๋ฐฉ์ถ |
import React from 'react';
export const HelloWorld = () => React.createElement("h1", null, "Hello world");
|
preserve๋ก ์ค์ ํ์ ๋๋ ๋์ํ์ง ์์์ง๋ง "react-jsx"๋ "react-jsxdev๋ก ์ค์ ํด ์คฌ์ ๋ ๋์ํ๋ค.
("react"๋ก ์ค์ ์ ์๋์ ๊ฐ์ ์๋ฌ ๋ฐ์)
components/__test__/themeButton.test.tsx:34:33 - error TS2686: 'React' refers to a UMD global, but the current file is a module. Consider adding an import instead.
์ด๋ jest๊ฐ ํ์ผ ํ์ฑ์ ํ ์ ์์ด์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ค. ๊ทธ๋ฅ jsx ํ์ผ(preserve)์ด ์๋๋ผ jest๊ฐ ์ธ์ํ ์ ์๋ ์ฝ๋๋ก ts ์ค์ ์ ๋ณ๊ฒฝํด ์ฃผ๋ (react-jsxdev) ์ ๋์ํ๊ฒ ๋์๋ค.
๐ References
https://turbo.build/repo/docs/guides/tools/jest
TSConfig Reference - Docs on every TSConfig option
From allowJs to useDefineForClassFields the TSConfig reference includes information about all of the active compiler flags setting up a TypeScript project.
www.typescriptlang.org
https://www.typescriptlang.org/tsconfig/#jsx
Jest | Turborepo
Learn how to use Jest in a Turborepo.
turbo.build