고체를 시험하고 있다.js 코드는 농담을 초월한다


그래서 Solid.js 와TypeScript로 응용 프로그램이나 라이브러리를 작성하기 시작하는 것은 좋은 선택이지만, 지금은 복귀를 피하기 위해 가능한 한 빨리 모든 것에 대해 단원 테스트를 하고 싶다.
우리는 이미 알고 있지만, 비록 그것은 매우 편리하고 설치하기 쉽지만, 그것도 상당히 느리고, 어떤 사람들은 자신의 의견을 고집한다.더 가벼운 테스트 실행 프로그램과 달리, 내장된 코드 변환 API, jsdom 기반의 DOM 환경, 기본 선택 browser 조건을 내보냅니다.
따라서 테스트는 jest 없이 실행되어야 합니다.
  • 코드 변환
  • DOM 환경
  • 선택browser 수출
  • 실수 레지스터


    더 많은 귀중한 시간을 절약하기 위해서, 나는 이미 너를 위해 이 모든 일을 했다.설치만 하면 돼요.
    npm i --save-dev solid-register jsdom
    
    함께 사용하다
    # test runner that supports the `-r` register argument
    $testrunner -r solid-register ...
    
    # test runner without support for the `r` argument
    node -r solid-register node_modules/.bin/$testrunner ...
    

    시주자


    농담 말고도 당신은 당연히 많은 선택을 할 수 있습니다.
  • uvu(가장 빠르지만 일부 기능이 없음)
  • tape(신속, 모듈화, 확장 가능, 많은 포크나 확장, 예를 들어supertape,tabe,tappedout)
  • ava(여전히 빠름)
  • bron(소형 폼 팩터, 기능 거의 없음, 빠른 속도)
  • karma(조금 느리지만 매우 성숙)
  • test-turtle(전체 테스트의 경우 속도가 조금 느리지만 마지막 실행 이후 실패하거나 변경된 파일만 테스트)
  • jasmine(jest 부분 기반 기능이 완비된 테스트 시스템)
  • 이 가능하다, ~할 수 있다,...나는 그것들을 모두 테스트할 수 없기 때문에 uvutape에 중점을 둘 것이다.둘 다register 파라미터를 지원하기 때문에 설치하기만 하면 됩니다
    npm -i --save-dev uvu
    # or
    npm -i --save-dev tape
    
    프로젝트에 스크립트를 추가하려면 다음과 같이 하십시오.
    {
      "scripts": {
        "test": "uvu -r solid-register"
      }
    }
    // or
    {
      "scripts": {
        "test": "tape -r solid-register"
      }
    }
    
    현재, 당신은 npm test를 사용하여 프로젝트에 대해 단원 테스트를 진행할 수 있습니다.

    The following examples are written for uvu, but it should be trivial adapting them to tape or any other test runner.


    사용자 정의 원어 테스트 (갈고리)


    만약 당신이 중복 사용할 수 있는 고체 반응 기능을 가지고 있다면.js는 어떤 내용도 과장하지 않기 때문에 사용할 필요가 없습니다 render().예를 들어, 여러 단어나 "Lorem ipsum"텍스트를 반환하는 함수를 테스트합니다.
    const loremIpsumWords = 'Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'.split(/\s+/);
    
    const createLorem = (words: Accessor<number> | number) => {
      return createMemo(() => {
        const output = [],
          len = typeof words === 'function' ? words() : words;
        while (output.length <= len) {
          output.push(...loremIpsumWords);
        }
    
        return output.slice(0, len).join(' ');
      });
    };
    
    우리는 구독 words 과 같은 액세서리를 허용하기 위해 테스트 동작을 수동 루트 디렉터리에 포장해야 한다.uvu에 대해 이렇게 (테이프에서 test 호출 수신의 첫 번째 매개 변수 중 다른 모든 것이 매우 비슷하다고 단언)할 수 있다.
    import { createEffect, createRoot, createSignal } from "solid-js";
    import { suite } from 'uvu';
    import * as assert from 'uvu/assert';
    
    const testLorem = suite('createLorem');
    
    testLorem('it updates the result when words update', async () => {
      const input = [3, 2, 5],
      expectedOutput = [
        'Lorem ipsum dolor',
        'Lorem ipsum',
        'Lorem ipsum dolor sit amet'
      ];
      const actualOutput = await new Promise<string[]>(resolve => createRoot(dispose => {
        const [words, setWords] = createSignal(input.shift() ?? 3);
        const lorem = createLorem(words);
    
        const output: string[] = [];
        createEffect(() => {
          // effects are batched, so the escape condition needs
          // to run after the output is complete:
          if (input.length === 0) {
            dispose();
            resolve(output);
          }
          output.push(lorem());
          setWords(input.shift() ?? 0);
        });
      }));
    
      assert.equal(actualOutput, expectedOutput, 'output differs');
    });
    
    testLorem.run();
    

    테스트 명령어 (사용:...)


    그런 다음 원어@solid-primitive/fullscreen를 테스트하여 명령을 수행하고 다음 API와 유사한 내용을 공개합니다.
    export type FullscreenDirective = (
      ref: HTMLElement,
      active: Accessor<boolean | FullscreenOptions>
    ) => void;
    
    고체에서는 이렇게 사용한다.js:
    const [fs, setFs] = createSignal(false);
    return <div use:FullscreenDirective={fs}>...</div>;
    
    디테일을 실현하지 않기를 원하기 때문에 상술한 구성 요소와 완전히 같지만, Solid의 디테일을 테스트해야 한다는 것을 의미하기 때문에, 우리는 어떤 내용도 보여줄 필요가 없습니다.js의 명령 인터페이스입니다.
    따라서 solid-primitives 저장소 중 하나look at the test를 보유할 수 있습니다.

    테스트 구성 요소


    우선 설치가 필요합니다solid-testing-library.불행하게도, 우리는 이곳에서 사용할 수 없지만, 제스트 @testing-library/jest-dom 의 주요 확장은 복제하기 쉽다.
    npm i --save-dev solid-testing-library
    
    다음과 같은 간단한 구성 요소를 테스트합니다.
    import { createSignal, Component, JSX } from 'solid-js';
    
    export const MyComponent: Component<JSX.HTMLAttributes<HTMLDivElement>> = (props) => {
      const [clicked, setClicked] = createSignal(false);
      return <div {...props} role="button" onClick={() => setClicked(true)}>
        {clicked() ? 'Test this!' : 'Click me!'}
      </div>;
    };
    
    현재 Dell의 테스트는 다음과 같습니다.
    import { suite } from 'uvu';
    import * as assert from 'uvu/assert';
    import { screen, render, fireEvent } from 'solid-testing-library';
    import { MyComponent } from './my-component';
    
    const isInDom = (node: Node): boolean => !!node.parentNode && 
      (node.parentNode === document || isInDom(node.parentNode));
    
    const test = suite('MyComponent');
    
    test('changes text on click', async () => {
      await render(() => <MyComponent />);
      const component = await screen.findByRole('button', { name: 'Click me!' });
      assert.ok(isInDom(component));
      fireEvent.click(component);
      assert.ok(isInDom(await screen.findByRole('button', { name: 'Test this!' })));
    });
    

    Running all three tests in uvu with the default settings takes ~1.6s whereas running them in jest using a fast babel-jest setup takes ~5.7s on my box.


    더 많은 부족한 기능

    expect보다 jestuvu 모두 더 많은 기능이 부족합니다.
  • 간단한 시뮬레이션/스파이
  • 타이머 시뮬레이션
  • 코드 커버율 집합
  • 시계 모드
  • 확장 가능한 단언
  • 스냅샷 테스트
  • tape로 많은 이 기능들은 외부 조수를 통해 추가할 수 있다.그 중 일부는 uvu 에 나타나는데 예를 들면 examples coverage , 그리고 일부는 기록되지 않았다. 예를 들어 watch 에 간첩을 추가했다.snoop에 대해 하나whole lot of modules가 있다.
    그러나 실행하지 않는 기능은 시간을 낭비하지 않는다는 것을 명심하세요.
    당신의 테스트가 모든 빈틈을 포착할 수 있기를 바랍니다!

    근데 내가 어떻게 했지?


    You can safely skip the next part if you are not interested in the details; you should already know enough to test your solid projects with something other than jest. The following code is a reduced version of solid-register to mainly show the underlying principles.


    코드 컴파일


    Node에는 파일tape에 연결하여 변환 코드를 로드하고 등록할 수 있는 API가 있습니다.
    우리는 세 가지 선택이 있다.

  • babel-registerbabel 전송 코드를 사용하고 있음;유형 검사는 신속하지만 지원되지 않음

  • ts-nodets서버 전송 코드를 사용하고 컴파일 시간을 희생하는 대가로 유형 안전성을 제공
  • 우리는 babel을 사용하여 우리만의 해결 방안을 내놓을 수 있다. 이것은 우리가 서로 다른 파일에 대해 서로 다른 사전 설정을 사용할 수 있게 한다
  • 바베타 레지스터


    babel 레지스터를 사용하려면 설치가 필요합니다
    npm i --save-dev @babel/core @babel/register \
    @babel/preset-env @babel/preset-typescript \
    babel-preset-solid
    
    이제 엔티티 파일을 컴파일하는 데 필요한 옵션과 결합하기 위해 require()에 이 기능을 사용해야 합니다.
    require('@babel/register')({
      "presets": [
        "@babel/preset-env",
        "babel-preset-solid",
        "@babel/preset-typescript"
      ],
      extensions: ['.jsx', '.tsx', '.ts', '.mjs']
    });
    

    ts 노드


    이 패키지의 주요 목적은 상호작용 typescript 컨트롤러를 제공하는 것이지만, 노드에서 typescript를 직접 실행할 수도 있습니다.다음과 같이 설치할 수 있습니다.
    npm i --save-dev ts-jest babel-preset-solid @babel/preset-env
    
    설치 후 compilation-babel.ts에서 사용할 수 있습니다.
    require('ts-node').register({ babelConfig: {
      presets: ['babel-preset-solid', '@babel/preset-env']
    } });
    

    Dell만의 솔루션


    왜 우리는 자신의 해결 방안을 원합니까?compilation-ts-node.tsbabel-register 모두 모듈을 컴파일하기 위해 미리 설정한 그룹만 설정할 수 있습니다. 이것은 일부 미리 설정이 헛되이 실행될 수 있음을 의미합니다. (예:.js 파일의 typescript 컴파일)또한 이 해결 방안이 처리하지 않은 문서를 처리할 수 있습니다. (장려장 참조)
    준비로 우리는 ts-jest 디렉터리를 만들고 그 중에서 우리의 리포를 초기화하고 우리의 요구를 설치합니다.
    npm init
    npm i --save-dev @babel/core @babel/preset-env \
    @babel/preset-typescript babel-preset-solid \
    typescript @types/node
    
    solid-registerbabel-register는 어떻게 자동으로 컴파일하여 가져옵니까?그들은 (불행하게도 버려졌고 문서도 불쌍하게도 적었지만 여전히 실행 가능했다)require.extensions API를 사용하여 노드의 모듈을 불러오는 과정에 자신을 주입했다.
    API는 상당히 간단합니다.
    // pseudo code to explain the API,
    // it's a bit more complex in reality:
    require.extensions[extension: string = '.js'] =
      (module: module, filename: string) => {
        const content = readFromCache(module)
          ?? fs.readFileSync(filename, 'UTF-8');
        module._compile(content, filename);
      };
    
    포장을 간소화하기 위해 우리는 다음과 같은 방법으로 자신의 ts-jest를 만들었고 이후에 다시 사용할 수 있다.
    export const registerExtension = (
      extension: string | string[],
      compile: (code: string, filename: string) => string
    ) => {
      if (Array.isArray(extension)) {
        extension.forEach(ext => registerExtension(ext, compile));
      } else {
        const modLoad = require.extensions[extension] ?? require.extensions['.js'];
        require.extensions[extension] = (module: NodeJS.Module, filename: string) => {
          const mod = module as NodeJS.Module  & { _compile: (code) => void };
          const modCompile = mod._compile.bind(mod);
          mod._compile = (code) => modCompile(compile(code, filename));
          modLoad(mod, filename);
        }
      }
    };
    
    이제 다음 내용을 포함하는 파일src/register-extension.ts을 생성하여 신뢰할 수 있는 코드를 컴파일할 수 있습니다.
    const { transformSync } = require('@babel/core');
    const presetEnv = require('@babel/preset-env');
    const presetSolid = require('babel-preset-solid');
    const presetTypeScript = require('@babel/preset-typescript');
    
    import { registerExtension } from "./register-extension";
    
    registerExtension('.jsx', (code, filename) =>
      transformSync(code, { filename, presets: [presetEnv, presetSolid] }));
    
    registerExtension('.ts', (code, filename) =>
      transformSync(code, { filename, presets: [presetEnv, presetTypeScript] }));
    
    registerExtension('.tsx', (code, filename) =>
      transformSync(code, { filename, presets: [presetEnv, presetSolid, presetTypeScript] }));
    

    보상 #1: 파일 이름 별칭


    만약 우리가 src/compile-solid.ts 로고를 사용하여 브라우저 버전을 선택하지 않으려면, 일부 파일 이름의 별명을 사용하여 노드가 실체에서 내보낸 브라우저를 선택하도록 강요할 수도 있다.이를 위해 우리는 --conditions을 창설했다.
    const aliases = {
      'solid-js\/dist\/server': 'solid-js/dist/dev',
      'solid-js\/web\/dist\/server': 'solid-js/web/dist/dev'
      // add your own here
    };
    const alias_regexes = Object.keys(aliases)
      .reduce((regexes, match) => { 
        regexes[match] = new RegExp(match);
        return regexes;
      }, {});
    const filenameAliasing = (filename) => 
      Object.entries(aliases).reduce(
        (name, [match, replace]) => 
          !name && alias_regexes[match].test(filename)
          ? filename.replace(alias_regexes[match], replace)
          : name,
        null) ?? filename;
    
    const extensions = ['.js', '.jsx', '.ts', '.tsx'];
    
    extensions.forEach(ext => {
      const loadMod = require.extensions[ext] ?? require.extensions['.js'];
      require.extensions[ext] = (module: NodeJS.Module, filename: string) => {
        loadMod(module, filenameAliasing(filename));
      };
    });
    

    보너스 #2: CSS 로더


    "file. css"를 가져올 때, 구축 시스템에 내부 마운트 프로그램을 사용하여 css 코드를 현재 페이지에 불러오는 것을 알려 줍니다. css 모듈이라면, 가져오는 데 클래스 이름을 제공합니다.src/compile-aliases.ts'.css'에 자신의 마운트 프로그램을 제공함으로써 우리는 노드에서 같은 체험을 할 수 있고 DOM의 실제 접근 스타일을 허용할 수 있다.
    따라서 Dell은 자체 코드'.module.css'로 다음 코드를 작성합니다.
    import { registerExtension } from "./register-extension";
    
    const loadStyles = (filename: string, styles: string) =>
      `if (!document.querySelector(\`[data-filename="${filename}"]\`)) {
      const div = document.createElement('div');
      div.innerHTML = \`<style data-filename="${filename}">${styles}</style>\`;
      document.head.appendChild(div.firstChild);
      styles.replace(/@import (["'])(.*?)\1/g, (_, __, requiredFile) => {
        try {
          require(requiredFile);
        } catch(e) {
          console.warn(\`attempt to @import css \${requiredFile}\` failed); }
        }
      });
    }`;
    
    const toCamelCase = (name: string): string =>
      name.replace(/[-_]+(\w)/g, (_, char) => char.toUpperCase());
    
    const getModuleClasses = (styles): Record<string, string> => {
      const identifiers: Record<string, string> = {};
      styles.replace(
        /(?:^|}[\r\n\s]*)(\.\w[\w-_]*)|@keyframes\s+([\{\s\r\n]+?)[\r\n\s]*\{/g,
        (_, classname, animation) => {
          if (classname) {
            identifiers[classname] = identifiers[toCamelCase(classname)] = classname;
          }
          if (animation) {
            identifiers[animation] = identifiers[toCamelCase(animation)] = animation;
          }
        }
      );
      return identifiers;
    };
    
    registerExtension('.css', (styles, filename) => loadStyles(filename, styles));
    registerExtension('.module.css', (styles, filename) =>
      `${loadStyles(filename, styles)}
    module.exports = ${JSON.stringify(getModuleClasses(styles))};`);
    

    보너스 #3: 자산 캐리어

    src/compile-css.ts 이니시에이터의 vite 서버에서 자산에서 경로를 가져올 수 있습니다.이제 연습을 시작해야 합니다. 직접 쓰셔도 됩니다solidjs/templates/ts:
    import { registerExtension } from "./register-extension";
    
    const assetExtensions = ['.svg', '.png', '.gif', '.jpg', '.jpeg'];
    
    registerExtension(assetExtensions, (_, filename) => 
      `module.exports = "./assets/${filename.replace(/.*\//, '')}";`
    );
    
    vite에서는 src/compile-assets.ts 개의 경로도 지원합니다.만약 네가 원한다면, 너는 이 부분을 확장해서 그들을 지지할 수 있다.본문을 작성할 때 ?raw 의 현재 버전은 그것을 지원하지 않습니다.

    DOM 환경


    컴파일링의 경우 DOM 환경에 대한 선택이 다릅니다.

  • jsdom, 기능이 모두 갖추어져 있지만 속도가 느린 농담의 기본 옵션

  • happy-dom, 더 가볍다

  • linkedom, 가장 빠르지만 기본 기능이 없음
  • 불행하게도 solid-register는 아직 전면적인 테스트를 거치지 않았고 happy-domlinkedom와 실제로 협조하여 사용할 수 없기 때문에 현재는 사용을 권장하지 않는다.

    jsdom


    jsdom는 기본적으로 이렇게 사용하기 때문에 등록은 매우 간단합니다.
    import { JSDOM } from 'jsdom';
    
    const { window } = new JSDOM(
      '<!doctype html><html><head></head><body></body></html>',
      { url: 'https://localhost:3000' }
    );
    Object.assign(globalThis, window);
    

    즐거운 돔


    import { Window } from 'happy-dom';
    
    const window = new Window();
    window.location.href = 'https://localhost:3000';
    
    for (const key of Object.keys(window)) {
      if ((globalThis as any)[key] === undefined && key !== 'undefined') {
        (globalThis as any)[key] = (window as any)[key];
      }
    }
    

    linkedom


    DOM 환경을 만들려면 다음과 같이 하십시오.
    // prerequisites
    const parseHTML = require('linkedom').parseHTML;
    const emptyHTML = `<!doctype html>
    <html lang="en">
      <head><title></title></head>
      <body></body>
    </html>`;
    
    // create DOM
    const {
        window,
        document,
        Node,
        HTMLElement,
        requestAnimationFrame,
        cancelAnimationFrame,
        navigator
    } = parseHTML(emptyHTML);
    
    // put DOM into global context
    Object.assign(globalThis, {
        window,
        document,
        Node,
        HTMLElement,
        requestAnimationFrame,
        cancelAnimationFrame,
        navigator
    });
    
    마지막으로, 너는 나처럼 이 모든 것을 설정 읽기 함수와 함께 놓을 수 있다.사용자 정의 Transpile 프레임워크에 유사한 패키지를 만들어야 한다면, 이 글을 무심코 읽으면 도움이 될 것입니다.
    당신의 인내심에 감사 드립니다. 나는 내가 너무 피곤하지 않기를 바랍니다.

    좋은 웹페이지 즐겨찾기