mirror of
https://github.com/theoludwig/theoludwig.git
synced 2024-12-08 00:44:30 +01:00
feat(posts): add clean-code
This commit is contained in:
parent
bcb184e49c
commit
0bf89f4df5
@ -1,25 +1,24 @@
|
|||||||
describe('Page /blog', () => {
|
describe('Page /blog', () => {
|
||||||
it('should displays the blog posts sorted from newest to oldest', () => {
|
it('should displays the blog posts sorted from newest to oldest', () => {
|
||||||
cy.visit('/blog')
|
cy.visit('/blog')
|
||||||
cy.get('[data-cy=blog-posts]:last-child [data-cy=blog-post-title]').should(
|
cy.get('[data-cy=blog-posts] [data-cy=blog-post-title]')
|
||||||
'have.text',
|
.last()
|
||||||
'Hello, world! 👋'
|
.should('have.text', 'Hello, world! 👋')
|
||||||
)
|
cy.get('[data-cy=blog-posts] [data-cy=blog-post-description]')
|
||||||
cy.get(
|
.last()
|
||||||
'[data-cy=blog-posts]:last-child [data-cy=blog-post-description]'
|
.should(
|
||||||
).should(
|
'have.text',
|
||||||
'have.text',
|
'First post of the blog, introduction and explanation of how this blog is made.'
|
||||||
'First post of the blog, introduction and explanation of how this blog is made.'
|
)
|
||||||
)
|
cy.get('[data-cy=blog-posts] [data-cy=blog-post-date]')
|
||||||
cy.get('[data-cy=blog-posts]:last-child [data-cy=blog-post-date]').should(
|
.last()
|
||||||
'have.text',
|
.should('have.text', '06/11/2021')
|
||||||
'06/11/2021'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should redirect the user to the right blog post', () => {
|
it('should redirect the user to the right blog post', () => {
|
||||||
cy.visit('/blog')
|
cy.visit('/blog')
|
||||||
cy.get('[data-cy=blog-posts]:last-child')
|
cy.get('[data-cy=blog-posts]')
|
||||||
|
.last()
|
||||||
.click()
|
.click()
|
||||||
.location('pathname')
|
.location('pathname')
|
||||||
.should('eq', '/blog/hello-world')
|
.should('eq', '/blog/hello-world')
|
||||||
|
330
package-lock.json
generated
330
package-lock.json
generated
@ -28,6 +28,7 @@
|
|||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"read-pkg": "7.0.0",
|
"read-pkg": "7.0.0",
|
||||||
|
"rehype-slug": "5.0.0",
|
||||||
"remark-gfm": "3.0.1",
|
"remark-gfm": "3.0.1",
|
||||||
"remark-prism": "1.3.6",
|
"remark-prism": "1.3.6",
|
||||||
"sharp": "0.29.2",
|
"sharp": "0.29.2",
|
||||||
@ -10938,6 +10939,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
|
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
|
||||||
},
|
},
|
||||||
|
"node_modules/github-slugger": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ=="
|
||||||
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "7.1.7",
|
"version": "7.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
||||||
@ -11293,6 +11299,27 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-has-property": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-4Qf++8o5v14us4Muv3HRj+Er6wTNGA/N9uCaZMty4JWvyFKLdhULrv4KE1b65AthsSO9TXSZnjuxS8ecIyhb0w==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hast-util-heading-rank": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-w+Rw20Q/iWp2Bcnr6uTrYU6/ftZLbHKhvc8nM26VIWpDqDMlku2iXUVTeOlsdoih/UKQhY7PHQ+vZ0Aqq8bxtQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hast-util-parse-selector": {
|
"node_modules/hast-util-parse-selector": {
|
||||||
"version": "2.2.5",
|
"version": "2.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
||||||
@ -11339,6 +11366,18 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hast-util-to-string": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hastscript": {
|
"node_modules/hastscript": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
|
||||||
@ -22931,6 +22970,169 @@
|
|||||||
"jsesc": "bin/jsesc"
|
"jsesc": "bin/jsesc"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rehype-slug": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-jnYsFKxRh+/tQa1L+SU/ykAPGOSVCqd0BwaOBPUANcvCu8d0/SZB4IalJkdJ+n6d1eAAS2YkvjUPi+2EGYtfCQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/hast": "^2.0.0",
|
||||||
|
"github-slugger": "^1.1.1",
|
||||||
|
"hast-util-has-property": "^2.0.0",
|
||||||
|
"hast-util-heading-rank": "^2.0.0",
|
||||||
|
"hast-util-to-string": "^2.0.0",
|
||||||
|
"unified": "^10.0.0",
|
||||||
|
"unist-util-visit": "^4.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/bail": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/is-buffer": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/is-plain-obj": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/trough": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/trough/-/trough-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w==",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/unified": {
|
||||||
|
"version": "10.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.0.tgz",
|
||||||
|
"integrity": "sha512-4U3ru/BRXYYhKbwXV6lU6bufLikoAavTwev89H5UxY8enDFaAT2VXmIXYNm6hb5oHPng/EXr77PVyDFcptbk5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"bail": "^2.0.0",
|
||||||
|
"extend": "^3.0.0",
|
||||||
|
"is-buffer": "^2.0.0",
|
||||||
|
"is-plain-obj": "^4.0.0",
|
||||||
|
"trough": "^2.0.0",
|
||||||
|
"vfile": "^5.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/unist-util-is": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/unist-util-stringify-position": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/unist-util-visit": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-is": "^5.0.0",
|
||||||
|
"unist-util-visit-parents": "^5.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/unist-util-visit-parents": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-is": "^5.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/vfile": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vfile/-/vfile-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-ftCpb6pU8Jrzcqku8zE6N3Gi4/RkDhRwEXSWudzZzA2eEOn/cBpsfk9aulCUR+j1raRSAykYQap9u6j6rhUaCA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"is-buffer": "^2.0.0",
|
||||||
|
"unist-util-stringify-position": "^3.0.0",
|
||||||
|
"vfile-message": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rehype-slug/node_modules/vfile-message": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-UUjZYIOg9lDRwwiBAuezLIsu9KlXntdxwG+nXnjuQAHvBpcX3x0eN8h+I7TkY5nkCXj+cWVp4ZqebtGBvok8ww==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-stringify-position": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/remark": {
|
"node_modules/remark": {
|
||||||
"version": "12.0.1",
|
"version": "12.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz",
|
||||||
@ -36584,6 +36786,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
|
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
|
||||||
},
|
},
|
||||||
|
"github-slugger": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ=="
|
||||||
|
},
|
||||||
"glob": {
|
"glob": {
|
||||||
"version": "7.1.7",
|
"version": "7.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
||||||
@ -36836,6 +37043,19 @@
|
|||||||
"web-namespaces": "^1.0.0"
|
"web-namespaces": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hast-util-has-property": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-4Qf++8o5v14us4Muv3HRj+Er6wTNGA/N9uCaZMty4JWvyFKLdhULrv4KE1b65AthsSO9TXSZnjuxS8ecIyhb0w=="
|
||||||
|
},
|
||||||
|
"hast-util-heading-rank": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-w+Rw20Q/iWp2Bcnr6uTrYU6/ftZLbHKhvc8nM26VIWpDqDMlku2iXUVTeOlsdoih/UKQhY7PHQ+vZ0Aqq8bxtQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/hast": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hast-util-parse-selector": {
|
"hast-util-parse-selector": {
|
||||||
"version": "2.2.5",
|
"version": "2.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
||||||
@ -36870,6 +37090,14 @@
|
|||||||
"zwitch": "^1.0.0"
|
"zwitch": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"hast-util-to-string": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==",
|
||||||
|
"requires": {
|
||||||
|
"@types/hast": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hastscript": {
|
"hastscript": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
|
||||||
@ -45395,6 +45623,108 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rehype-slug": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-jnYsFKxRh+/tQa1L+SU/ykAPGOSVCqd0BwaOBPUANcvCu8d0/SZB4IalJkdJ+n6d1eAAS2YkvjUPi+2EGYtfCQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/hast": "^2.0.0",
|
||||||
|
"github-slugger": "^1.1.1",
|
||||||
|
"hast-util-has-property": "^2.0.0",
|
||||||
|
"hast-util-heading-rank": "^2.0.0",
|
||||||
|
"hast-util-to-string": "^2.0.0",
|
||||||
|
"unified": "^10.0.0",
|
||||||
|
"unist-util-visit": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bail": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="
|
||||||
|
},
|
||||||
|
"is-buffer": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ=="
|
||||||
|
},
|
||||||
|
"is-plain-obj": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-NXRbBtUdBioI73y/HmOhogw/U5msYPC9DAtGkJXeFcFWSFZw0mCUsPxk/snTuJHzNKA8kLBK4rH97RMB1BfCXw=="
|
||||||
|
},
|
||||||
|
"trough": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/trough/-/trough-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-FnHq5sTMxC0sk957wHDzRnemFnNBvt/gSY99HzK8F7UP5WAbvP70yX5bd7CjEQkN+TjdxwI7g7lJ6podqrG2/w=="
|
||||||
|
},
|
||||||
|
"unified": {
|
||||||
|
"version": "10.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unified/-/unified-10.1.0.tgz",
|
||||||
|
"integrity": "sha512-4U3ru/BRXYYhKbwXV6lU6bufLikoAavTwev89H5UxY8enDFaAT2VXmIXYNm6hb5oHPng/EXr77PVyDFcptbk5g==",
|
||||||
|
"requires": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"bail": "^2.0.0",
|
||||||
|
"extend": "^3.0.0",
|
||||||
|
"is-buffer": "^2.0.0",
|
||||||
|
"is-plain-obj": "^4.0.0",
|
||||||
|
"trough": "^2.0.0",
|
||||||
|
"vfile": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unist-util-is": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ=="
|
||||||
|
},
|
||||||
|
"unist-util-stringify-position": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-SdfAl8fsDclywZpfMDTVDxA2V7LjtRDTOFd44wUJamgl6OlVngsqWjxvermMYf60elWHbxhuRCZml7AnuXCaSA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/unist": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unist-util-visit": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-is": "^5.0.0",
|
||||||
|
"unist-util-visit-parents": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unist-util-visit-parents": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg==",
|
||||||
|
"requires": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-is": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vfile": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vfile/-/vfile-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-ftCpb6pU8Jrzcqku8zE6N3Gi4/RkDhRwEXSWudzZzA2eEOn/cBpsfk9aulCUR+j1raRSAykYQap9u6j6rhUaCA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"is-buffer": "^2.0.0",
|
||||||
|
"unist-util-stringify-position": "^3.0.0",
|
||||||
|
"vfile-message": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vfile-message": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-UUjZYIOg9lDRwwiBAuezLIsu9KlXntdxwG+nXnjuQAHvBpcX3x0eN8h+I7TkY5nkCXj+cWVp4ZqebtGBvok8ww==",
|
||||||
|
"requires": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-stringify-position": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"remark": {
|
"remark": {
|
||||||
"version": "12.0.1",
|
"version": "12.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/remark/-/remark-12.0.1.tgz",
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"read-pkg": "7.0.0",
|
"read-pkg": "7.0.0",
|
||||||
|
"rehype-slug": "5.0.0",
|
||||||
"remark-gfm": "3.0.1",
|
"remark-gfm": "3.0.1",
|
||||||
"remark-prism": "1.3.6",
|
"remark-prism": "1.3.6",
|
||||||
"sharp": "0.29.2",
|
"sharp": "0.29.2",
|
||||||
|
@ -34,9 +34,14 @@ const BlogPostPage: React.FC<BlogPostPageProps> = (props) => {
|
|||||||
<MDXRemote
|
<MDXRemote
|
||||||
{...post.source}
|
{...post.source}
|
||||||
components={{
|
components={{
|
||||||
a: (props: React.ComponentPropsWithoutRef<'a'>) => (
|
a: (props: React.ComponentPropsWithoutRef<'a'>) => {
|
||||||
<a target='_blank' rel='noopener noreferrer' {...props} />
|
if (props.href?.startsWith('#') ?? false) {
|
||||||
)
|
return <a {...props} />
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<a target='_blank' rel='noopener noreferrer' {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
254
posts/clean-code.mdx
Normal file
254
posts/clean-code.mdx
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
---
|
||||||
|
title: 'Clean Code 🧼'
|
||||||
|
description: 'What is "Clean Code", what are "Design Patterns", and why is it so important today ? Tips and tricks to make your code more readable and maintainable in the long term.'
|
||||||
|
isPublished: false
|
||||||
|
publishedOn: '2021-11-06T22:06:33.818Z'
|
||||||
|
---
|
||||||
|
|
||||||
|
Hello! 👋
|
||||||
|
|
||||||
|
Have you already heard of "**Clean Code**" or "**Design Patterns**" ?
|
||||||
|
|
||||||
|
Even if you know what it is about, this blog post will probably still be useful to you, I will share some tips and tricks to make your code more readable and maintainable in the long term.
|
||||||
|
|
||||||
|
**Note:** Sources used to write this blog post are available at the [end of this post](#sources).
|
||||||
|
|
||||||
|
## Definition : Clean Code
|
||||||
|
|
||||||
|
A clean code is a code that is **easy** to **read** and easy to **understand**.
|
||||||
|
|
||||||
|
But I promise it is not a code that is easy to write, in fact it is really **hard** to **write Clean Code**.
|
||||||
|
|
||||||
|
We could ask ourselves, what is **easy** to **read** and **easy** to **understand** ?
|
||||||
|
|
||||||
|
It depends of many factors, and is somewhat relative to each one of us. The **perfect** Clean code **doesn't exist**, but we can try to be **as perfect as possible**.
|
||||||
|
|
||||||
|
## Why is it so important ?
|
||||||
|
|
||||||
|
Code that works is great, but it is not enough, even if the code will be read by the computer and understood by the machine, we should not forget that the code is **human written** and will be also **read by a human** not only a machine.
|
||||||
|
|
||||||
|
For example the [Linux kernel](https://www.kernel.org/), is one of the biggest open source project with many contributors worldwide. Last data shows that it is about **20 millions** lines of code.
|
||||||
|
|
||||||
|
With a project of this magnitude, we can't let everyone do what they wants and however they wants, **we must set rules and conventions** to get everyone to agree, this allows to add features faster and will reduce possible bugs as **developers** will not struggle as much to understand the code.
|
||||||
|
|
||||||
|
## Definition : Design Patterns
|
||||||
|
|
||||||
|
These **rules** and **conventions** are so called **Design Patterns**.
|
||||||
|
|
||||||
|
A software design pattern is a general way of **solving a problem** by applying a **well-known solution**.
|
||||||
|
|
||||||
|
Design patterns are formalized **best practices** that the programmer can use to solve common problems when designing an application or system.
|
||||||
|
|
||||||
|
## How to write Clean Code and famous Design Patterns
|
||||||
|
|
||||||
|
To show you the rules and conventions, I will write the examples in the [TypeScript](https://www.typescriptlang.org/) programming language but it is relevant to any programming language.
|
||||||
|
|
||||||
|
### Naming variables
|
||||||
|
|
||||||
|
We all know that **variables** are used everywhere in **programming**, good variable names allow us to better understand the intention of the code.
|
||||||
|
|
||||||
|
#### Same vocabulary for the same type of variable
|
||||||
|
|
||||||
|
##### Example (bad way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getUserInfo(): User
|
||||||
|
function getUserDetails(): User
|
||||||
|
function getUserData(): User
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Example (good way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function getUser(): User
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Avoid "Magic Numbers"
|
||||||
|
|
||||||
|
##### Example (bad way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// What does 86400000 mean ?
|
||||||
|
setTimeout(restart, 86400000)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Example (good way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000
|
||||||
|
setTimeout(restart, MILLISECONDS_IN_A_DAY)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Explicit is better than implicit (no abbreviations or acronyms)
|
||||||
|
|
||||||
|
##### Example (bad way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const u = getUser()
|
||||||
|
const s = getSubscription()
|
||||||
|
const t = charge(u, s)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Example (good way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const user = getUser()
|
||||||
|
const subscription = getSubscription()
|
||||||
|
const transaction = charge(user, subscription)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### As short as possible, as long as necessary
|
||||||
|
|
||||||
|
##### Example (bad way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Car {
|
||||||
|
carModel: string
|
||||||
|
carColor: 'red' | 'blue' | 'yellow'
|
||||||
|
}
|
||||||
|
const printCar = (car: Car): void => {
|
||||||
|
console.log(`${car.carModel} (${car.carColor})`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Example (good way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Car {
|
||||||
|
model: string
|
||||||
|
color: 'red' | 'blue' | 'yellow'
|
||||||
|
}
|
||||||
|
const printCar = (car: Car): void => {
|
||||||
|
console.log(`${car.model} (${car.color})`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Boolean names (Prefix: is, has, can)
|
||||||
|
|
||||||
|
##### Example (bad way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
let person = true
|
||||||
|
let age = true
|
||||||
|
let dance = true
|
||||||
|
function isEmailNotUsed(email: string): boolean
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Example (good way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
let isPerson = true
|
||||||
|
let hasAge = true
|
||||||
|
let canDance = true
|
||||||
|
function isEmailUsed(email: string): boolean
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### DRY (Don't Repeat Yourself)
|
||||||
|
|
||||||
|
When we copy/paste the same lines of code, we should better abstract it in a function, that we can reuse later without having to copy/paste the lines of code, that makes the code more maintainable afterwards, because if we need to change the behavior of this piece of code, we won't need to change it in several places, but only when declaring the function.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### KISS (Keep It Simple Stupid)
|
||||||
|
|
||||||
|
As we have just said, we will prefer to abstract the code in multiple functions, rather than leaving everything in the same place, but a function should not do "too much", and we should rather separate it into several distinct functions.
|
||||||
|
|
||||||
|
We have to keep it as simple as possible, not to implement features that are not requested, and to divide the functions as much as possible into small functions.
|
||||||
|
|
||||||
|
### Example (bad way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
const createFile = async (name: string, isTemporary: boolean = false) => {
|
||||||
|
if (isTemporary) {
|
||||||
|
return await fs.promises.writeFile(path.join('temporary', name), '')
|
||||||
|
}
|
||||||
|
return await fs.promises.writeFile(name, '')
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`createFile` is a function that does 2 things so it is better to split it in 2 separated functions.
|
||||||
|
|
||||||
|
### Example (good way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
const createFile = async (name: string, isTemporary: boolean = false) => {
|
||||||
|
await fs.promises.writeFile(name, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTemporaryFile = async (name: string) => {
|
||||||
|
await createFile(path.join('temporary', name))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### TDD (Test Driven Development)
|
||||||
|
|
||||||
|
Test-driven development (TDD) is a software development process relying on software requirements being converted to test cases before software is fully developed, and tracking all software development by repeatedly testing the software against all test cases. This is as opposed to software being developed first and test cases created later.
|
||||||
|
|
||||||
|
We first write tests that should fails because there are no implementation, and then we write the code implementation to make the tests succeeds.
|
||||||
|
|
||||||
|
The End To End (e2e) and Unit tests should document what is the behavior intended for the code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Avoid comments
|
||||||
|
|
||||||
|
One of the most important rule of "Clean Code" : If you need to add **comments**, it's because your code is **not clean**.
|
||||||
|
|
||||||
|
I know that might be counter intuitive at first, as most developers will advice you to add comments to your code, to document what it does.
|
||||||
|
|
||||||
|
The thing is that you should choose good variable names, break down features in multiple functions, so that others developers can read your code and understand it just by reading the functions names etc.
|
||||||
|
|
||||||
|
You can write comments, but that should only be used documenting how to use a function but not for the implementation itself and in places where you can't be more explicit.
|
||||||
|
|
||||||
|
In fact, as we saw in the [TDD section](#tdd-test-driven-development), automated tests can document what a function should returns, and how the code should behave, so that should already improve code maintainability.
|
||||||
|
|
||||||
|
Having a good comment explaining a difficult code is better than nothing with a bad written code, difficult to understand.
|
||||||
|
|
||||||
|
#### Example (bad way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Check if subscription is active
|
||||||
|
if (subscription.endDate > Date.now()) {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example (good way)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const isSubscriptionActive = subscription.endDate > Date.now()
|
||||||
|
if (isSubscriptionActive) {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we are creating a new variable `isSubscriptionActive` that allows us to avoid the need of a comment to understand what the code does.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
We can't write the perfect clean code understandable by everyone but we can **write code that is as perfect as possible to ease maintaibility** of the code by others developers (and for yourself).
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- [Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin](https://books.google.fr/books/about/Clean_Code.html?id=hjEFCAAAQBAJ)
|
||||||
|
- [Software Design Pattern (Wikipedia)](https://en.wikipedia.org/wiki/Software_design_pattern)
|
||||||
|
- [TDD - Test-driven development (Wikipedia)](https://en.wikipedia.org/wiki/Test-driven_development)
|
||||||
|
- [github.com/labs42io/clean-code-typescript](https://github.com/labs42io/clean-code-typescript)
|
@ -19,6 +19,16 @@ I don't plan to publish new posts regularly, but I will do so when I have someth
|
|||||||
|
|
||||||
To stay informed of new blog post and to ask questions, feel free to follow me on Twitter: [@Divlo_FR](https://twitter.com/Divlo_FR).
|
To stay informed of new blog post and to ask questions, feel free to follow me on Twitter: [@Divlo_FR](https://twitter.com/Divlo_FR).
|
||||||
|
|
||||||
|
## Project based learning
|
||||||
|
|
||||||
|
The blog posts subjects will be often related to the problems I encountered in the projects I am currently working on.
|
||||||
|
|
||||||
|
Most of the time, when I am learning something new, I **learn it because I actually need it for a project**, I don't learn [React.js](https://reactjs.org) because it is trending, and everyone talks about it.
|
||||||
|
|
||||||
|
I learn it, because it solved a "real life" problem I had encountered. For example, [React.js](https://reactjs.org) allows to easily update the DOM (Document Object Model) in the browser, so we can add interactivity to our web pages, not only that, it allows to reuse multiple HTML (JSX) elements with components.
|
||||||
|
|
||||||
|
[React.js](https://reactjs.org) is only a example, but hopefully you understood my point: I often don't like too much theoretical thing, and enjoy much more practical things.
|
||||||
|
|
||||||
## How this blog is made
|
## How this blog is made
|
||||||
|
|
||||||
In this section, I will explain what technologies I used to make this blog, and what are the technical choices I had to do.
|
In this section, I will explain what technologies I used to make this blog, and what are the technical choices I had to do.
|
||||||
@ -53,3 +63,9 @@ That not mean that theses features will never be implemented, but to avoid the n
|
|||||||
- [Tailwind CSS](https://tailwindcss.com/)
|
- [Tailwind CSS](https://tailwindcss.com/)
|
||||||
|
|
||||||
Tailwind is a CSS framework to rapidly build modern websites without ever leaving HTML.
|
Tailwind is a CSS framework to rapidly build modern websites without ever leaving HTML.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
I hope you will enjoy my blog, and I hope you will find it useful.
|
||||||
|
|
||||||
|
See you in the next posts! 😊
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
@apply text-gray dark:text-gray-dark !max-w-4xl;
|
@apply text-gray dark:text-gray-dark !max-w-4xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose a {
|
.prose a,
|
||||||
|
.prose strong {
|
||||||
@apply text-yellow dark:text-yellow-dark;
|
@apply text-yellow dark:text-yellow-dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import type { MDXRemoteSerializeResult } from 'next-mdx-remote'
|
|||||||
import { serialize } from 'next-mdx-remote/serialize'
|
import { serialize } from 'next-mdx-remote/serialize'
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
import remarkPrism from 'remark-prism'
|
import remarkPrism from 'remark-prism'
|
||||||
|
import rehypeSlug from 'rehype-slug'
|
||||||
import matter from 'gray-matter'
|
import matter from 'gray-matter'
|
||||||
|
|
||||||
export const postsPath = path.join(process.cwd(), 'posts')
|
export const postsPath = path.join(process.cwd(), 'posts')
|
||||||
@ -63,7 +64,8 @@ export const getPostBySlug = async (
|
|||||||
}
|
}
|
||||||
const source = await serialize(post.content, {
|
const source = await serialize(post.content, {
|
||||||
mdxOptions: {
|
mdxOptions: {
|
||||||
remarkPlugins: [remarkGfm as any, remarkPrism]
|
remarkPlugins: [remarkGfm as any, remarkPrism],
|
||||||
|
rehypePlugins: [rehypeSlug as any]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return { ...post, source }
|
return { ...post, source }
|
||||||
|
Loading…
Reference in New Issue
Block a user