Skip to content

Commit cb6bcb4

Browse files
authored
Merge pull request #547 from Jessepriase/feat/416-automated-formatting-checks
Feat/416 automated formatting checks
2 parents ab6b1e9 + fd54fcd commit cb6bcb4

13 files changed

Lines changed: 362 additions & 15 deletions

.editorconfig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.{ts,tsx,js,jsx,cjs,mjs,json,yaml,yml,md}]
10+
indent_style = space
11+
indent_size = 2
12+
13+
[*.rs]
14+
indent_style = space
15+
indent_size = 4
16+
17+
[Makefile]
18+
indent_style = tab
19+
20+
[*.md]
21+
trim_trailing_whitespace = false

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,33 @@ on:
88
- staging
99

1010
jobs:
11+
format:
12+
name: Formatting checks
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Setup Node
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: 22
21+
22+
- name: Install dashboard dependencies
23+
working-directory: dashboard
24+
run: npm ci
25+
26+
- name: Check dashboard formatting (Prettier)
27+
working-directory: dashboard
28+
run: npm run format:check
29+
30+
- name: Install listener dependencies
31+
working-directory: listener
32+
run: npm ci
33+
34+
- name: Check listener formatting (Prettier)
35+
working-directory: listener
36+
run: npm run format:check
37+
1138
frontend:
1239
name: Frontend (lint, typecheck, test)
1340
runs-on: ubuntu-latest

.prettierignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Dependencies
2+
node_modules/
3+
dashboard/node_modules/
4+
listener/node_modules/
5+
6+
# Build output
7+
dist/
8+
dashboard/dist/
9+
listener/dist/
10+
target/
11+
12+
# Lock files (managed by package managers, not formatted)
13+
package-lock.json
14+
dashboard/package-lock.json
15+
listener/package-lock.json
16+
Cargo.lock
17+
18+
# Generated / vendored
19+
*.wasm
20+
*.optimized.wasm
21+
reports/
22+
dashboard/reports/
23+
24+
# Environment files
25+
.env
26+
.env.*

.prettierrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"semi": true,
3+
"singleQuote": true,
4+
"trailingComma": "all",
5+
"printWidth": 100,
6+
"tabWidth": 2,
7+
"useTabs": false,
8+
"arrowParens": "always",
9+
"endOfLine": "lf"
10+
}

CODE_FORMATTING.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Code Formatting
2+
3+
NotifyChain enforces consistent formatting across all languages via automated checks that run on every pull request.
4+
5+
---
6+
7+
## Tools
8+
9+
| Language | Tool | Config |
10+
|---|---|---|
11+
| TypeScript / TSX (dashboard) | [Prettier](https://prettier.io) 3.x | `.prettierrc` (root) |
12+
| TypeScript (listener) | [Prettier](https://prettier.io) 3.x | `.prettierrc` (root) |
13+
| Rust (contracts) | `rustfmt` (stable) | default `rustfmt` rules |
14+
| All files | EditorConfig | `.editorconfig` (root) |
15+
16+
---
17+
18+
## Prettier rules (TypeScript / TSX)
19+
20+
Defined in `.prettierrc` at the repository root.
21+
22+
| Rule | Value |
23+
|---|---|
24+
| `semi` | `true` — semicolons required |
25+
| `singleQuote` | `true` — single quotes for strings |
26+
| `trailingComma` | `"all"` — trailing commas wherever valid |
27+
| `printWidth` | `100` — wrap lines at 100 characters |
28+
| `tabWidth` | `2` — two-space indentation |
29+
| `useTabs` | `false` — spaces, not tabs |
30+
| `arrowParens` | `"always"` — parentheses around arrow function parameters |
31+
| `endOfLine` | `"lf"` — Unix line endings |
32+
33+
---
34+
35+
## Rust formatting
36+
37+
The `rust` CI job runs `cargo fmt --all -- --check`. This uses the default `rustfmt` rules (stable channel). No custom `rustfmt.toml` is required.
38+
39+
---
40+
41+
## EditorConfig
42+
43+
`.editorconfig` enforces baseline rules in supported editors (VS Code, JetBrains, Vim, etc.) independently of any formatter:
44+
45+
- UTF-8 encoding everywhere
46+
- LF line endings everywhere
47+
- Final newline on every file
48+
- Trailing whitespace trimmed (except Markdown)
49+
- 2-space indentation for TS/JS/JSON/YAML/Markdown
50+
- 4-space indentation for Rust
51+
52+
---
53+
54+
## CI enforcement
55+
56+
The `format` job in `.github/workflows/ci.yml` runs on every pull request and push to `main` / `staging`:
57+
58+
```
59+
format job
60+
├── dashboard: npm run format:check (Prettier --check)
61+
└── listener: npm run format:check (Prettier --check)
62+
```
63+
64+
The `rust` job also runs `cargo fmt --all -- --check`.
65+
66+
A pull request **cannot be merged** if either check exits non-zero.
67+
68+
---
69+
70+
## Fixing formatting locally
71+
72+
### TypeScript / TSX
73+
74+
```bash
75+
# Fix dashboard
76+
cd dashboard
77+
npx prettier --write "src/**/*.{ts,tsx}" --config ../.prettierrc
78+
79+
# Fix listener
80+
cd listener
81+
npx prettier --write "src/**/*.ts" --config ../.prettierrc
82+
```
83+
84+
### Rust
85+
86+
```bash
87+
cd contract
88+
cargo fmt --all
89+
```
90+
91+
### VS Code auto-format on save
92+
93+
Install the [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) extension and add to `.vscode/settings.json`:
94+
95+
```json
96+
{
97+
"editor.formatOnSave": true,
98+
"editor.defaultFormatter": "esbenp.prettier-vscode",
99+
"[rust]": {
100+
"editor.defaultFormatter": "rust-lang.rust-analyzer"
101+
}
102+
}
103+
```

dashboard/package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"preview": "node ./node_modules/vite/bin/vite.js preview",
99
"dev": "node ./node_modules/vite/bin/vite.js",
1010
"lint": "node ./node_modules/eslint/bin/eslint.js \"src/**/*.{ts,tsx}\" --max-warnings=0",
11+
"format:check": "node ./node_modules/prettier/bin/prettier.cjs --check \"src/**/*.{ts,tsx}\" --config ../.prettierrc",
1112
"test": "node ./node_modules/jest/bin/jest.js",
1213
"test:wallet": "node ./node_modules/jest/bin/jest.js src/__tests__/wallet-integration.test.tsx",
1314
"benchmark": "node ./node_modules/jest/bin/jest.js src/benchmark"
@@ -19,25 +20,23 @@
1920
"zustand": "^5.0.6"
2021
},
2122
"devDependencies": {
22-
"@typescript-eslint/eslint-plugin": "^6.10.0",
23-
"@typescript-eslint/parser": "^6.10.0",
24-
"eslint": "^8.46.0",
25-
"eslint-plugin-react": "^7.33.0",
26-
"@testing-library/react": "^16.3.0",
27-
"@testing-library/user-event": "^14.6.1",
28-
"@types/jest": "^29.5.14",
2923
"@testing-library/jest-dom": "^6.9.1",
3024
"@testing-library/react": "^16.3.2",
3125
"@testing-library/user-event": "^14.6.1",
3226
"@types/jest": "^29.5.14",
3327
"@types/jest-axe": "^3.5.9",
3428
"@types/react": "^19.1.8",
3529
"@types/react-dom": "^19.1.6",
30+
"@typescript-eslint/eslint-plugin": "^6.10.0",
31+
"@typescript-eslint/parser": "^6.10.0",
3632
"@vitejs/plugin-react": "^4.7.0",
33+
"eslint": "^8.46.0",
34+
"eslint-plugin-react": "^7.33.0",
3735
"jest": "^29.7.0",
3836
"jest-axe": "^10.0.0",
3937
"jest-environment-jsdom": "^29.7.0",
4038
"jsdom": "^26.1.0",
39+
"prettier": "3.3.3",
4140
"ts-jest": "^29.2.5",
4241
"typescript": "^5.8.3",
4342
"vite": "^6.3.5"

dashboard/src/App.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,82 @@ export function App() {
129129
</div>
130130
</div>
131131
</div>
132+
<nav className="app-tabs" role="tablist" aria-label="Main navigation">
133+
<button
134+
type="button"
135+
role="tab"
136+
aria-selected={tab === 'explorer'}
137+
className={`app-tabs__btn${tab === 'explorer' ? ' app-tabs__btn--active' : ''}`}
138+
onClick={() => setTab('explorer')}
139+
>
140+
Event Explorer
141+
</button>
142+
<button
143+
type="button"
144+
role="tab"
145+
aria-selected={tab === 'timeline'}
146+
className={`app-tabs__btn${tab === 'timeline' ? ' app-tabs__btn--active' : ''}`}
147+
onClick={() => setTab('timeline')}
148+
>
149+
Delivery Timeline
150+
</button>
151+
<button
152+
type="button"
153+
role="tab"
154+
aria-selected={tab === 'activity'}
155+
className={`app-tabs__btn${tab === 'activity' ? ' app-tabs__btn--active' : ''}`}
156+
onClick={() => setTab('activity')}
157+
>
158+
Activity Feed
159+
</button>
160+
<button
161+
type="button"
162+
role="tab"
163+
aria-selected={tab === 'webhooks'}
164+
className={`app-tabs__btn${tab === 'webhooks' ? ' app-tabs__btn--active' : ''}`}
165+
onClick={() => setTab('webhooks')}
166+
>
167+
Webhook Performance
168+
</button>
169+
<button
170+
type="button"
171+
role="tab"
172+
aria-selected={tab === 'export-history'}
173+
className={`app-tabs__btn${tab === 'export-history' ? ' app-tabs__btn--active' : ''}`}
174+
onClick={() => setTab('export-history')}
175+
>
176+
Export History
177+
</button>
178+
<button
179+
type="button"
180+
role="tab"
181+
aria-selected={tab === 'search'}
182+
className={`app-tabs__btn${tab === 'search' ? ' app-tabs__btn--active' : ''}`}
183+
onClick={() => setTab('search')}
184+
>
185+
Notification Search
186+
</button>
187+
<button
188+
type="button"
189+
role="tab"
190+
aria-selected={tab === 'preferences'}
191+
className={`app-tabs__btn${tab === 'preferences' ? ' app-tabs__btn--active' : ''}`}
192+
onClick={() => setTab('preferences')}
193+
>
194+
Preferences
195+
</button>
196+
<button
197+
type="button"
198+
role="tab"
199+
aria-selected={tab === 'templates'}
200+
className={`app-tabs__btn${tab === 'templates' ? ' app-tabs__btn--active' : ''}`}
201+
onClick={() => setTab('templates')}
202+
>
203+
Templates
204+
</button>
205+
</nav>
206+
207+
{tab === 'explorer' && (
132208
export function App() {
133209
const [tab, setTab] = useState<Tab>('explorer');
134210
const [drawerOpen, setDrawerOpen] = useState(false);

dashboard/src/__tests__/wallet-integration.test.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import * as fs from 'fs';
1010
import * as path from 'path';
1111

1212
const WALLET_ID_KEY = 'notify-chain:wallet-id';
13-
const WALLET_ADDRESS_KEY = 'notify-chain:wallet-address';
1413
const REPORT_PATH = path.join(process.cwd(), 'reports', 'wallet-integration.json');
1514

1615
type KitMock = typeof import('../test/stellarWalletsKitMock');
@@ -239,7 +238,7 @@ describe('Notification feed clears on wallet switch (issue #175)', () => {
239238
await wallet.disconnectWallet();
240239

241240
expect(store.useWalletStore.getState().address).toBeNull();
242-
expect(localStorage.getItem(WALLET_ADDRESS_KEY)).toBeNull();
241+
expect(localStorage.getItem('notify-chain:wallet-address')).toBeNull();
243242
});
244243

245244
it('no stale address remains in localStorage after switching wallets', async () => {
@@ -258,8 +257,8 @@ describe('Notification feed clears on wallet switch (issue #175)', () => {
258257
await wallet.connectWallet();
259258

260259
// localStorage must reflect the new account only
261-
expect(localStorage.getItem(WALLET_ADDRESS_KEY)).toBe(SUPPORTED_WALLETS[2].address);
262-
expect(localStorage.getItem(WALLET_ADDRESS_KEY)).not.toBe(SUPPORTED_WALLETS[0].address);
260+
expect(localStorage.getItem('notify-chain:wallet-address')).toBe(SUPPORTED_WALLETS[2].address);
261+
expect(localStorage.getItem('notify-chain:wallet-address')).not.toBe(SUPPORTED_WALLETS[0].address);
263262
expect(store.useWalletStore.getState().address).toBe(SUPPORTED_WALLETS[2].address);
264263
});
265264

@@ -283,7 +282,7 @@ describe('Notification feed clears on wallet switch (issue #175)', () => {
283282
await wallet.connectWallet();
284283

285284
expect(store.useWalletStore.getState().address).toBe(provider.address);
286-
expect(localStorage.getItem(WALLET_ADDRESS_KEY)).toBe(provider.address);
285+
expect(localStorage.getItem('notify-chain:wallet-address')).toBe(provider.address);
287286
expect(localStorage.getItem(WALLET_ID_KEY)).toBe(provider.id);
288287

289288
localStorage.clear();

0 commit comments

Comments
 (0)