Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the first integration test #124

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
5 changes: 5 additions & 0 deletions .changeset/flat-months-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'steiger': patch
---

The diagnostics from a single rule are now sorted by path in alphabetical order
4 changes: 2 additions & 2 deletions .github/workflows/bundle-size-trusted.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ jobs:
}
}
const fs = require('fs');
const sizes = parseDuOutput(fs.readFileSync(`sizes-${${{ toJson(github.base_ref) }}}.txt`, 'utf8'));
const sizesPR = parseDuOutput(fs.readFileSync(`sizes-${${{ toJson(github.head_ref) }}}.txt`, 'utf8'));
const sizes = parseDuOutput(fs.readFileSync('sizes-master.txt', 'utf8'));
const sizesPR = parseDuOutput(fs.readFileSync(`sizes-${${{ toJson(github.event.workflow_run.head_branch) }}}.txt`, 'utf8'));
core.summary.addHeading('📊 Package size report', '3');
core.summary.addTable([
['Package', 'Before', 'After'].map((data) => ({ data, header: true })),
Expand Down
1 change: 1 addition & 0 deletions examples/kitchen-sink-of-fsd-issues/src/app/ui/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const App = {}
1 change: 1 addition & 0 deletions examples/kitchen-sink-of-fsd-issues/src/app/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { App } from './App'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { App } from '@/app/ui/App'

console.log(App)
12 changes: 12 additions & 0 deletions examples/kitchen-sink-of-fsd-issues/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "ESNext",
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"]
}
5 changes: 4 additions & 1 deletion examples/kitchen-sink-of-fsd-issues/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
{}
{
"files": [],
"references": [{ "path": "./tsconfig.app.json" }]
}
1 change: 1 addition & 0 deletions integration-tests/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@steiger/eslint-config'
37 changes: 37 additions & 0 deletions integration-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"private": true,
"name": "@steiger/integration-tests",
"description": "Integration tests for Steiger",
"version": "0.1.0",
"scripts": {
"test": "vitest run",
"update-windows-snapshots": "node ./scripts/update-windows-snapshots.mjs"
},
"exports": {
".": "./src/index.ts"
},
"type": "module",
"authors": [
{
"name": "Lev Chelyadinov",
"email": "[email protected]",
"url": "https://github.com/illright"
}
],
"devDependencies": {
"@steiger/eslint-config": "workspace:*",
"@steiger/tsconfig": "workspace:*",
"@total-typescript/ts-reset": "^0.6.1",
"@types/node": "^18.11.9",
"eslint": "^9.16.0",
"figures": "^6.1.0",
"get-bin-path": "^11.0.0",
"prettier": "^3.4.2",
"tinyexec": "^0.3.1",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
},
"dependencies": {
"steiger": "workspace:*"
}
}
2 changes: 2 additions & 0 deletions integration-tests/reset.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Do not add any other lines of code to this file!
import '@total-typescript/ts-reset'
23 changes: 23 additions & 0 deletions integration-tests/scripts/update-windows-snapshots.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Run this script on Mac or Linux when you update test snapshots to port these changes over to Windows snapshots.
*
* $ pnpm run update-windows-snapshots
*/

import * as fs from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { replaceSymbols } from 'figures'

const snapshotsFolder = join(dirname(fileURLToPath(import.meta.url)), '../tests/__snapshots__')

// Go through each POSIX snapshot and replace the forward slashes in diagnostic paths with backslashes
for (const file of await fs.readdir(snapshotsFolder)) {
if (file.endsWith('-posix.txt')) {
const windowsSnapshotName = file.slice(0, -'-posix.txt'.length) + '-windows.txt'
const updatedSnapshot = replaceSymbols(await fs.readFile(join(snapshotsFolder, file), 'utf8'), {
useFallback: true,
}).replace(/(?<=┌.+)\//gm, '\\')
await fs.writeFile(join(snapshotsFolder, windowsSnapshotName), updatedSnapshot)
}
}
45 changes: 45 additions & 0 deletions integration-tests/tests/__snapshots__/smoke-stderr-posix.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

┌ src/entities/user/api/getUser.ts
✘ Forbidden import from higher layer "app".
└ fsd/forbidden-imports (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/forbidden-imports​)

┌ src/entities
✘ Inconsistent pluralization of slice names. Prefer all plural names
✔ Auto-fixable
└ fsd/inconsistent-naming (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/inconsistent-naming​)

┌ src/entities/user
✘ This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src/entities/users
✘ This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src/entities/user/api/getUser.ts
✘ Forbidden sidestep of public API when importing from "@/app/ui/App".
└ fsd/no-public-api-sidestep (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-public-api-sidestep​)

┌ src/entities/user/ui/api
✘ Having a folder with the name "api" inside a segment could be confusing because that name is commonly used for segments. Consider renaming it.
└ fsd/no-reserved-folder-names (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-reserved-folder-names​)

┌ src/app/ui
✘ Layer "app" should not have "ui" segment.
└ fsd/no-ui-in-app (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-ui-in-app​)

┌ src/processes
✘ Layer "processes" is deprecated, avoid using it
└ fsd/no-processes (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-processes​)

────────────────────────────────────────────────────────
Found 8 errors (1 can be fixed automatically with --fix)

45 changes: 45 additions & 0 deletions integration-tests/tests/__snapshots__/smoke-stderr-windows.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

┌ src\entities\user\api\getUser.ts
× Forbidden import from higher layer "app".
└ fsd/forbidden-imports (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/forbidden-imports​)

┌ src\entities
× Inconsistent pluralization of slice names. Prefer all plural names
√ Auto-fixable
└ fsd/inconsistent-naming (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/inconsistent-naming​)

┌ src\entities\user
× This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src\entities\users
× This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src\entities\user\api\getUser.ts
× Forbidden sidestep of public API when importing from "@/app/ui/App".
└ fsd/no-public-api-sidestep (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-public-api-sidestep​)

┌ src\entities\user\ui\api
× Having a folder with the name "api" inside a segment could be confusing because that name is commonly used for segments. Consider renaming it.
└ fsd/no-reserved-folder-names (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-reserved-folder-names​)

┌ src\app\ui
× Layer "app" should not have "ui" segment.
└ fsd/no-ui-in-app (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-ui-in-app​)

┌ src\processes
× Layer "processes" is deprecated, avoid using it
└ fsd/no-processes (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-processes​)

────────────────────────────────────────────────────────
Found 8 errors (1 can be fixed automatically with --fix)

24 changes: 24 additions & 0 deletions integration-tests/tests/smoke.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as fs from 'node:fs/promises'
import os from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { exec } from 'tinyexec'

import { expect, test } from 'vitest'

import { getSteigerBinPath } from '../utils/get-bin-path.js'

const temporaryDirectory = await fs.realpath(os.tmpdir())
const steiger = await getSteigerBinPath()
const kitchenSinkExample = join(dirname(fileURLToPath(import.meta.url)), '../../examples/kitchen-sink-of-fsd-issues')
const pathPlatform = os.platform() === 'win32' ? 'windows' : 'posix'

test('basic functionality in the kitchen sink example project', async () => {
const project = join(temporaryDirectory, 'smoke')
await fs.rm(project, { recursive: true, force: true })
await fs.cp(kitchenSinkExample, project, { recursive: true })

const { stderr } = await exec('node', [steiger, 'src'], { nodeOptions: { cwd: project, env: { NO_COLOR: '1' } } })

await expect(stderr).toMatchFileSnapshot(join('__snapshots__', `smoke-stderr-${pathPlatform}.txt`))
})
7 changes: 7 additions & 0 deletions integration-tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@steiger/tsconfig/base.json",
"include": ["./tests", "./reset.d.ts", "utils/get-bin-path.ts"],
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
}
}
22 changes: 22 additions & 0 deletions integration-tests/utils/get-bin-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { promises as fs } from 'node:fs'
import * as process from 'node:process'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { getBinPath } from 'get-bin-path'

/**
* Resolve the full path to the built JS file of Steiger.
*
* Rejects if the file doesn't exist.
*/
export async function getSteigerBinPath() {
const steiger = (await getBinPath({ cwd: join(dirname(fileURLToPath(import.meta.url)), '../../packages/steiger') }))!
try {
await fs.stat(steiger)
} catch {
console.error('Run `turbo build` before running integration tests')
process.exit(1)
}

return steiger
}
21 changes: 9 additions & 12 deletions packages/steiger/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,22 @@ async function runRules({ vfs, rules }: { vfs: Folder; rules: Array<Rule> }) {
const vfsWithoutGlobalIgnores = removeGlobalIgnoreFromVfs(vfs, getGlobalIgnores())

const ruleResults = await Promise.all(rules.map((rule) => runRule(vfsWithoutGlobalIgnores, rule)))
return ruleResults.flatMap((r, ruleResultsIndex) => {
const { diagnostics } = r
if (diagnostics.length === 0) {
return []
}
Comment on lines -26 to -28
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: I removed this part while I was adding the sorting because I found several return statements to be confusing and because I believe this isn't that impactful from the performance perspective. The empty array will simply get processed as any other array, with no work done on it


return ruleResults.flatMap(({ diagnostics }, ruleResultsIndex) => {
const ruleName = rules[ruleResultsIndex].name
const severities = calculateFinalSeverities(
vfsWithoutGlobalIgnores,
ruleName,
diagnostics.map((d) => d.location.path),
)

return diagnostics.map((d, index) => ({
...d,
ruleName,
getRuleDescriptionUrl,
severity: severities[index],
}))
return diagnostics
.sort((a, b) => a.location.path.localeCompare(b.location.path))
.map((d, index) => ({
...d,
ruleName,
getRuleDescriptionUrl,
severity: severities[index],
}))
})
}

Expand Down
Loading
Loading