お知らせ

現在サイトのリニューアル作業中のため、全体的にページの表示が乱れています。

条件分岐でコンポーネントの出し分けをしている時に正しくコンポーネントが出ているかどうかに使えるやつ
UIロジックのリグレッションテストで使える
分岐結果の出力を見てるだけなのでテストとして壊れづらく、運用しやすいと考えている

確認環境

Next.jsで確認してるけど素のReactでも同じだと思う

Env Ver
@swc/core 1.2.133
@swc/jest 0.2.17
jest 27.4.7
next 12.0.8
react 17.0.2
react-dom 17.0.2
react-test-renderer 17.0.2
typescript 4.5.4

テスト対象

テストのためにコンポーネントを細かくexportすると名前空間が汚染されるのが悩み…

type BaseProps = {
  id: string;
};

type SwitchExampleProps = BaseProps & {
  display: 'Foo' | 'Bar';
};

export const Foo = (props: BaseProps) => {
  return (
    <div id={props.id}>
      <p>Foo</p>
    </div>
  );
};

export const Bar = (props: BaseProps) => {
  return (
    <div id={props.id}>
      <p>Bar</p>
    </div>
  );
};

export const SwitchExample = (props: SwitchExampleProps) => {
  if (props.display === 'Foo') {
    return <Foo id={props.id} />;
  } else {
    return <Bar id={props.id} />;
  }
};

テストコード

react-testing-libraryの.toHaveAttribute().toHaveDisplayValue()を書き連ねるより圧倒的に楽で保守性も良いと思う

import TestRenderer from 'react-test-renderer';
import { Bar, Foo, SwitchExample } from './SwitchExample';

type TestCase = {
  name: string;
  param: Parameters<typeof SwitchExample>[0];
  actual: JSX.Element;
};

describe('SwitchExample', () => {
  const testCaseItems: TestCase[] = [
    {
      name: 'Foo',
      param: {
        id: 'hoge',
        display: 'Foo',
      },
      actual: <Foo id={'hoge'} />,
    },
    {
      name: 'Bar',
      param: {
        id: 'piyo',
        display: 'Bar',
      },
      actual: <Bar id={'piyo'} />,
    },
  ];

  testCaseItems.forEach((item) => {
    // eslint-disable-next-line jest/valid-title
    it(`switched condition ${item.name}`, () => {
      const result = TestRenderer.create(
        <SwitchExample id={item.param.id} display={item.param.display} />
      );
      const actual = TestRenderer.create(<>{item.actual}</>);

      expect(result.toJSON()).toStrictEqual(actual.toJSON());
    });
  });
});

参考記事

はてブ数
投稿日:
サービス::GitHub::GitHub Actions開発::自動化

GitHub Actionsで前のジョブの結果を後続で利用したい時に使えるやつ

サンプルコード

  • 適当な文字列を変数にセットして、各ジョブで出力する例
name: outputs sharing example
on:
  pull_request:
jobs:
  first:
    runs-on: ubuntu-latest
    outputs:
      # <out-job-name>: ${{ steps.<step-id>.outputs.<in-job-name> }}
      baz: ${{ steps.foo.outputs.bar }}
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - id: foo
        # この場合、FOO-BARという値がセットされる
        run: echo "::set-output name=bar::$(echo FOO-BAR)"
      - id: test
        # steps.<step-id>.outputs.<in-job-name>
        run: echo "${{ steps.foo.outputs.bar }}"
  second:
    needs: [first]
    runs-on: ubuntu-latest
    steps:
      - id: test
        # needs.<job-id>.outputs.<out-job-name>
        run: echo "${{ needs.first.outputs.baz }}"

参考

はてブ数
投稿日:
ジャンル::サイト運営言語::Markdown

Markdownを使って快適にブログを書きたくない?書きたいですよね?そう、書きたい!
ではどうすればいいか?というのをこれから書いていきます。
JAMStackを使ってGitHub PagesとNext.js SSGでMarkdownブログを作ろう!みたいな人はブラウザバックをオススメします。

技術選定

  • JAMStack
  • はてなブログ
  • WordPress

JAMStack

まずJAMStackは真っ先に除外します。ブログを書きたいのであってブログを作りたいわけではないからです。

またSSGだとクライアントサイドのレンダリングコストが重く、リッチなブログにしようとすると負荷がしんどい気がしています。恐らくSEO対策的にもCSRを妥協して極限までSSGしていないと厳しいのではないでしょうか?

はてなブログ

カスタマイズがすんごくだるい上に、真面目にやろうとするとクライアントサイドでHTMLをゴリゴリ書き換えたり、orderなどのハックに近いCSSを多用して擬似的に要素を移動させたりする必要があり、レイアウトを組むのが非常に面倒です。

エディタもしょぼいしカテゴリはないし(はてブロのカテゴリは実質的にタグなので)年会費が高い割に出来ることの制限が強い…。あんまSEOも強くなかったし、試しに使ってみたものの流入が激減…正直個人的にこれはですね。

WordPress

多分こいつが一番現実的な選択肢です。WP Githuber MDを使うことで不自由なくMarkdownを扱うことが出来ると思います。但し内蔵のPrism.jsはなんかいい感じに動いてくれないケースがあり、その場合はカスタマイズが必要です。

カスタマイズについてもプラグインやテーマが豊富にあり、改造も容易なのでローコストでリッチなものが実現しやすいと思います。またセルフホストなのでデータの取り回しの自由が効きやすいのも大きなポイントですね。

CMSは色々使ってきましたが、なんだかんだブログはWordPressが一番だなって思います。SEO対策もGoogle謹製のやつがあるし、特に何もしなくてもある程度なんとかなるのは強みだと思います。

カスタマイズ法

CSS

使っているテーマと整合性が取れるようにいい感じにします。私は Dracula for Prism.js を改造して使いました。

JS

私の場合は次のコードをヘッダタグの中に足して対処しました。特に内容の説明はしませんが、行番号とコピーボタンが出るようにしています。

<script src="https://unpkg.com/prismjs@1.28.0/components/prism-core.min.js"></script>
<script src="https://unpkg.com/prismjs@1.28.0/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://unpkg.com/prismjs@1.28.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
<script src="https://unpkg.com/prismjs@1.28.0/plugins/toolbar/prism-toolbar.min.js"></script>
<script src="https://unpkg.com/prismjs@1.28.0/plugins/copy-to-clipboard/prism-copy-to-clipboard.min.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', () => {
    document.querySelectorAll('pre > code').forEach((codeEl) => {
      codeEl.className = codeEl.className === '' ? 'language-none' : codeEl.className;
      codeEl.className = codeEl.className.replace(/^sh$/, 'shell');
      codeEl.parentNode.className = 'line-numbers';
    });

    window.Prism.highlightAll();
  });
</script>
はてブ数
投稿日:
ソフトウェア::VSCode
{
    "terminal.integrated.defaultProfile.windows": "MSYS2",
    "terminal.integrated.profiles.windows": {
        "MSYS2": {
            "overrideName": true,
            "path": ["C:\\env\\msys64\\msys2_shell.cmd"],
            "args": [
                "-defterm",
                "-here",
                "-use-full-path",
                "-no-start",
                "-mingw64",
                "-shell",
                "zsh"
            ]
        },
        "PowerShell": {
            "source": "PowerShell",
            "icon": "terminal-powershell"
        },
        "Command Prompt": {
            "path": [
                "${env:windir}\\Sysnative\\cmd.exe",
                "${env:windir}\\System32\\cmd.exe"
            ],
            "args": [],
            "icon": "terminal-cmd"
        }
    },
    "terminal.integrated.defaultProfile.linux": "zsh",
    "terminal.integrated.profiles.linux": {
        "zsh": {
            "path": "zsh"
        },
        "bash": {
            "path": "bash"
        }
    },
    "terminal.integrated.allowChords": false,
    "workbench.startupEditor": "newUntitledFile",
    "workbench.iconTheme": "vscode-icons",
    "workbench.editor.decorations.badges": false,
    "workbench.editor.decorations.colors": false,
    "files.eol": "\n",
    "files.trimTrailingWhitespace": true,
    "files.insertFinalNewline": true,
    "git.autofetch": true,
    "git.autorefresh": true,
    "git.autoStash": true,
    "git.suggestSmartCommit": false,
    "diffEditor.ignoreTrimWhitespace": true,
    "explorer.confirmDragAndDrop": false,
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": "explicit"
    },
    "[typescript]": {
        "editor.tabSize": 2
    },
    "[json]": {
        "editor.tabSize": 2,
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[javascript]": {
        "editor.tabSize": 2,
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "[vue]": {
        "editor.tabSize": 2
    },
    "[html]": {
        "editor.tabSize": 2,
        "editor.formatOnSave": true
    },
    "[css]": {
        "editor.tabSize": 2,
        "editor.formatOnSave": true
    },
    "[scss]": {
        "editor.tabSize": 2,
        "editor.formatOnSave": true
    },
    "[python]": {
        "editor.tabSize": 4,
        "editor.formatOnSave": true
    },
    "[markdown]": {
        "editor.tabSize": 4,
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.formatOnSave": true
    },
    "php.validate.run": "onSave",
    "javascript.preferences.quoteStyle": "single",
    "javascript.updateImportsOnFileMove.enabled": "always",
    "typescript.updateImportsOnFileMove.enabled": "always",
    "typescript.preferences.quoteStyle": "single",
    "prettier.htmlWhitespaceSensitivity": "strict",
    "prettier.singleQuote": true,
    "prettier.tabWidth": 4,
    "prettier.useTabs": false,
    "prettier.bracketSpacing": true,
    "vsicons.dontShowNewVersionMessage": true,
    "pasteImage.path": "${currentFileDir}/${currentFileNameWithoutExt}.assets",
    "todo-tree.filtering.excludeGlobs": ["**/node_modules/**/*"],
    "todo-tree.highlights.customHighlight": {
        "TODO": {
            "foreground": "#f8ff96",
            "type": "text-and-comment"
        },
        "FIXME": {
            "foreground": "#ff9696",
            "type": "text-and-comment"
        }
    },
    "todo-tree.general.tags": ["TODO", "FIXME"],
    "todo-tree.regex.regex": "(//|#|<!--|/\\*|^\\s*\\*)\\s*($TAGS)",
    "gitlens.currentLine.format": "${author, }${date}${' via 'pullRequest}${ • message|50?}",
    "gitlens.statusBar.format": "${author}, ${date}${' via 'pullRequest}",
    "gitlens.statusBar.tooltipFormat": "${avatar} &nbsp;__${author}__, ${date}${' via 'pullRequest}\n\n${message}${\n\n---\n\nfootnotes}\n\n${commands}",
    "gitlens.hovers.detailsMarkdownFormat": "${avatar} &nbsp;__${author}__, ${date}${' via 'pullRequest}\n\n${message}${\n\n---\n\nfootnotes}\n\n${commands}",
    "gitlens.views.formats.stashes.description": "${date}",
    "gitlens.views.formats.commits.description": "${author, }${date}",
    "gitlens.defaultDateFormat": "YYYY-MM-DD",
    "terminal.integrated.shellIntegration.decorationsEnabled": "never",
    "git.mergeEditor": false,
    "security.workspace.trust.untrustedFiles": "open",
    "explorer.copyRelativePathSeparator": "/",
    "githubPullRequests.pullBranch": "never",
    "workbench.layoutControl.enabled": false,
    "githubPullRequests.terminalLinksHandler": "github",
    "git.openRepositoryInParentFolders": "never",
    "update.showReleaseNotes": false,
    "workbench.editor.empty.hint": "hidden",
    "typescript.tsserver.log": "off",
    "gitlens.ai.experimental.generateCommitMessage.enabled": false,
    "scm.showIncomingChanges": "never",
    "scm.showOutgoingChanges": "never",
}