mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
ESLint plugin defaults to flat config, and we also dropped the built-in lint check at https://github.com/vercel/next.js/pull/83136, so we can sunset the deprecated ESLint v8 testing.
499 lines
13 KiB
TypeScript
499 lines
13 KiB
TypeScript
/* eslint-env jest */
|
|
import { Linter } from 'eslint'
|
|
import { rules } from '@next/eslint-plugin-next'
|
|
import assert from 'assert'
|
|
import path from 'path'
|
|
|
|
const NextESLintRule = rules['no-html-link-for-pages']
|
|
|
|
const withCustomPagesDir = path.join(__dirname, 'with-custom-pages-dir')
|
|
const withNestedPagesDir = path.join(__dirname, 'with-nested-pages-dir')
|
|
const withoutPagesDir = path.join(__dirname, 'without-pages-dir')
|
|
const withAppDir = path.join(__dirname, 'with-app-dir')
|
|
|
|
const linters = {
|
|
withoutPages: new Linter({
|
|
cwd: withoutPagesDir,
|
|
configType: 'eslintrc',
|
|
}),
|
|
withApp: new Linter({
|
|
cwd: withAppDir,
|
|
configType: 'eslintrc',
|
|
}),
|
|
withNestedPages: new Linter({
|
|
cwd: withNestedPagesDir,
|
|
configType: 'eslintrc',
|
|
}),
|
|
withCustomPages: new Linter({
|
|
cwd: withCustomPagesDir,
|
|
configType: 'eslintrc',
|
|
}),
|
|
}
|
|
|
|
const linterConfig: any = {
|
|
rules: {
|
|
'no-html-link-for-pages': [2],
|
|
},
|
|
parserOptions: {
|
|
ecmaVersion: 2018,
|
|
sourceType: 'module',
|
|
ecmaFeatures: {
|
|
modules: true,
|
|
jsx: true,
|
|
},
|
|
},
|
|
}
|
|
const linterConfigWithCustomDirectory: any = {
|
|
...linterConfig,
|
|
rules: {
|
|
'no-html-link-for-pages': [
|
|
2,
|
|
path.join(withCustomPagesDir, 'custom-pages'),
|
|
],
|
|
},
|
|
}
|
|
const linterConfigWithMultipleDirectories = {
|
|
...linterConfig,
|
|
rules: {
|
|
'no-html-link-for-pages': [
|
|
2,
|
|
[
|
|
path.join(withCustomPagesDir, 'custom-pages'),
|
|
path.join(withCustomPagesDir, 'custom-pages/list'),
|
|
],
|
|
],
|
|
},
|
|
}
|
|
const linterConfigWithNestedContentRootDirDirectory = {
|
|
...linterConfig,
|
|
settings: {
|
|
next: {
|
|
rootDir: path.join(withNestedPagesDir, 'demos/with-nextjs'),
|
|
},
|
|
},
|
|
}
|
|
|
|
for (const linter of Object.values(linters)) {
|
|
linter.defineRules({
|
|
'no-html-link-for-pages': NextESLintRule,
|
|
})
|
|
}
|
|
|
|
const validCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<Link href='/'>
|
|
<a>Homepage</a>
|
|
</Link>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const validAnchorCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='#heading'>Homepage</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const validExternalLinkCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='https://google.com/'>Homepage</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const validDownloadLinkCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='/static-file.csv' download>Download</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
const validTargetBlankLinkCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a target="_blank" href='/new-tab'>New Tab</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const validPublicFile = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='/presentation.pdf'>View PDF</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const invalidStaticCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='/'>Homepage</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const invalidDynamicCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='/list/foo/bar'>Homepage</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
const secondInvalidDynamicCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='/list/foo/'>Homepage</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const thirdInvalidDynamicCode = `
|
|
import Link from 'next/link';
|
|
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='/list/lorem-ipsum/'>Homepage</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
const validInterceptedRouteCode = `
|
|
import Link from 'next/link';
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<Link href='/photo/1/'>Photo</Link>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
|
|
const invalidInterceptedRouteCode = `
|
|
import Link from 'next/link';
|
|
export class Blah extends Head {
|
|
render() {
|
|
return (
|
|
<div>
|
|
<a href='/photo/1/'>Photo</a>
|
|
<h1>Hello title</h1>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
`
|
|
describe('no-html-link-for-pages', function () {
|
|
it('does not print warning when there are "pages" or "app" directories with rootDir in context settings', function () {
|
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
|
linters.withNestedPages.verify(
|
|
validCode,
|
|
linterConfigWithNestedContentRootDirDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
expect(consoleSpy).not.toHaveBeenCalled()
|
|
consoleSpy.mockRestore()
|
|
})
|
|
it('prints warning when there are no "pages" or "app" directories', function () {
|
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
|
linters.withoutPages.verify(validCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
expect(consoleSpy).toHaveBeenCalledWith(
|
|
`Pages directory cannot be found at ${path.join(
|
|
withoutPagesDir,
|
|
'pages'
|
|
)} or ${path.join(
|
|
withoutPagesDir,
|
|
'src',
|
|
'pages'
|
|
)}. If using a custom path, please configure with the \`no-html-link-for-pages\` rule in your eslint config file.`
|
|
)
|
|
consoleSpy.mockRestore()
|
|
})
|
|
it('does not print warning when there is "app" directory and no "pages" directory', function () {
|
|
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
|
|
linters.withApp.verify(validCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
expect(consoleSpy).not.toHaveBeenCalled()
|
|
consoleSpy.mockRestore()
|
|
})
|
|
it('valid link element', function () {
|
|
const report = linters.withCustomPages.verify(
|
|
validCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid link element with multiple directories', function () {
|
|
const report = linters.withCustomPages.verify(
|
|
validCode,
|
|
linterConfigWithMultipleDirectories,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid anchor element', function () {
|
|
const report = linters.withCustomPages.verify(
|
|
validAnchorCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid external link element', function () {
|
|
const report = linters.withCustomPages.verify(
|
|
validExternalLinkCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid download link element', function () {
|
|
const report = linters.withCustomPages.verify(
|
|
validDownloadLinkCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid target="_blank" link element', function () {
|
|
const report = linters.withCustomPages.verify(
|
|
validTargetBlankLinkCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid public file link element', function () {
|
|
const report = linters.withCustomPages.verify(
|
|
validPublicFile,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('invalid static route', function () {
|
|
const [report] = linters.withCustomPages.verify(
|
|
invalidStaticCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.notEqual(report, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
report.message,
|
|
'Do not use an `<a>` element to navigate to `/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
})
|
|
it('invalid dynamic route', function () {
|
|
const [report] = linters.withCustomPages.verify(
|
|
invalidDynamicCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.notEqual(report, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
report.message,
|
|
'Do not use an `<a>` element to navigate to `/list/foo/bar/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
const [secondReport] = linters.withCustomPages.verify(
|
|
secondInvalidDynamicCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.notEqual(secondReport, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
secondReport.message,
|
|
'Do not use an `<a>` element to navigate to `/list/foo/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
const [thirdReport] = linters.withCustomPages.verify(
|
|
thirdInvalidDynamicCode,
|
|
linterConfigWithCustomDirectory,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.notEqual(thirdReport, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
thirdReport.message,
|
|
'Do not use an `<a>` element to navigate to `/list/lorem-ipsum/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
})
|
|
it('valid link element with appDir', function () {
|
|
const report = linters.withApp.verify(validCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid link element with multiple directories with appDir', function () {
|
|
const report = linters.withApp.verify(validCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid anchor element with appDir', function () {
|
|
const report = linters.withApp.verify(validAnchorCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid external link element with appDir', function () {
|
|
const report = linters.withApp.verify(validExternalLinkCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid download link element with appDir', function () {
|
|
const report = linters.withApp.verify(validDownloadLinkCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid target="_blank" link element with appDir', function () {
|
|
const report = linters.withApp.verify(
|
|
validTargetBlankLinkCode,
|
|
linterConfig,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('valid public file link element with appDir', function () {
|
|
const report = linters.withApp.verify(validPublicFile, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('invalid static route with appDir', function () {
|
|
const [report] = linters.withApp.verify(invalidStaticCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.notEqual(report, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
report.message,
|
|
'Do not use an `<a>` element to navigate to `/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
})
|
|
it('invalid dynamic route with appDir', function () {
|
|
const [report] = linters.withApp.verify(invalidDynamicCode, linterConfig, {
|
|
filename: 'foo.js',
|
|
})
|
|
assert.notEqual(report, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
report.message,
|
|
'Do not use an `<a>` element to navigate to `/list/foo/bar/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
const [secondReport] = linters.withApp.verify(
|
|
secondInvalidDynamicCode,
|
|
linterConfig,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.notEqual(secondReport, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
secondReport.message,
|
|
'Do not use an `<a>` element to navigate to `/list/foo/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
const [thirdReport] = linters.withApp.verify(
|
|
thirdInvalidDynamicCode,
|
|
linterConfig,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.notEqual(thirdReport, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
thirdReport.message,
|
|
'Do not use an `<a>` element to navigate to `/list/lorem-ipsum/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
})
|
|
it('valid intercepted route with appDir', function () {
|
|
const report = linters.withApp.verify(
|
|
validInterceptedRouteCode,
|
|
linterConfig,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.deepEqual(report, [])
|
|
})
|
|
it('invalid intercepted route with appDir', function () {
|
|
const [report] = linters.withApp.verify(
|
|
invalidInterceptedRouteCode,
|
|
linterConfig,
|
|
{ filename: 'foo.js' }
|
|
)
|
|
assert.notEqual(report, undefined, 'No lint errors found.')
|
|
assert.equal(
|
|
report.message,
|
|
'Do not use an `<a>` element to navigate to `/photo/1/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
|
|
)
|
|
})
|
|
})
|