- 投稿日:
条件分岐でコンポーネントの出し分けをしている時に正しくコンポーネントが出ているかどうかに使えるやつ
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 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を使って快適にブログを書きたくない?書きたいですよね?そう、書きたい!
ではどうすればいいか?というのをこれから書いていきます。
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>
- 投稿日:
{
"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.automationProfile.windows": {
"path": "${env:windir}\\System32\\cmd.exe",
"args": [],
"icon": "terminal-cmd"
},
"terminal.integrated.allowChords": false,
"terminal.integrated.commandsToSkipShell": [
"-workbench.action.quickOpenView",
"-workbench.action.terminal.focusFind"
],
"workbench.startupEditor": "newUntitledFile",
"workbench.iconTheme": "vscode-icons",
"workbench.editor.decorations.badges": false,
"workbench.editor.decorations.colors": false,
"workbench.tree.enableStickyScroll": false,
"workbench.layoutControl.enabled": false,
"workbench.editor.empty.hint": "hidden",
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"scm.showIncomingChanges": "never",
"scm.showOutgoingChanges": "never",
"git.autorefresh": true,
"git.autoStash": true,
"git.suggestSmartCommit": false,
"git.mergeEditor": false,
"git.openRepositoryInParentFolders": "never",
"remote.autoForwardPortsSource": "hybrid",
"diffEditor.ignoreTrimWhitespace": true,
"diffEditor.renderGutterMenu": false,
"explorer.confirmDragAndDrop": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.stickyScroll.enabled": false,
"[markdown]": {
"editor.tabSize": 4,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"php.validate.run": "onSave",
"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} __${author}__, ${date}${' via 'pullRequest}\n\n${message}${\n\n---\n\nfootnotes}\n\n${commands}",
"gitlens.hovers.detailsMarkdownFormat": "${avatar} __${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",
"security.workspace.trust.untrustedFiles": "open",
"explorer.copyRelativePathSeparator": "/",
"typescript.tsserver.log": "off",
"gitlens.ai.experimental.generateCommitMessage.enabled": false,
"redhat.telemetry.enabled": true
}