Skip to content

Commit 537e217

Browse files
committed
test: jest setup and unit tests
1 parent 71b91f8 commit 537e217

File tree

5 files changed

+224
-2
lines changed

5 files changed

+224
-2
lines changed

jest.config.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
export default {
3+
preset: 'ts-jest/presets/default-esm',
4+
testEnvironment: 'node',
5+
extensionsToTreatAsEsm: ['.ts'],
6+
moduleNameMapper: {
7+
'^(\\.{1,2}/.*)\\.js$': '$1',
8+
},
9+
transform: {
10+
'^.+\\.tsx?$': [
11+
'ts-jest',
12+
{
13+
useESM: true,
14+
},
15+
],
16+
},
17+
rootDir: '.',
18+
testMatch: ['<rootDir>/tests/**/*.test.ts'],
19+
collectCoverageFrom: [
20+
'src/**/*.ts',
21+
'!src/**/*.d.ts',
22+
'!src/**/index.ts',
23+
'!src/**/structures.ts',
24+
],
25+
coverageDirectory: 'coverage',
26+
coverageReporters: ['text', 'lcov', 'html'],
27+
}

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@
2929
"fix": "npm run lint -- --fix",
3030
"lint": "npx eslint src/**",
3131
"prepare": "husky || true",
32-
"test": "npm run lint",
32+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
33+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
34+
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
35+
"test:ci": "NODE_OPTIONS=--experimental-vm-modules jest --ci --coverage --reporters=default --reporters=jest-junit",
3336
"upgrade:all": "npx npm-check-updates -u && npm install"
3437
},
3538
"license": "ISC",
@@ -52,6 +55,7 @@
5255
"@vercel/ncc": "^0.38.3",
5356
"husky": "9.1.7",
5457
"jest": "^29.7.0",
58+
"jest-junit": "^16.0.0",
5559
"make-coverage-badge": "^1.2.0",
5660
"ts-jest": "^29.3.0",
5761
"ts-node": "^10.9.2",

tests/utils/misc.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { getAgeBasedEmoji, getRelativeHumanReadableAge, haveOrHas } from '../../src/utils/misc.js'
2+
3+
describe('Utils: Misc', () => {
4+
describe('getAgeBasedEmoji', () => {
5+
it('should return empty string for hours <= 8', () => {
6+
expect(getAgeBasedEmoji(8)).toBe('')
7+
expect(getAgeBasedEmoji(7)).toBe('')
8+
expect(getAgeBasedEmoji(0)).toBe('')
9+
})
10+
11+
it('should return a random emoji for hours > 8', () => {
12+
const result = getAgeBasedEmoji(9)
13+
expect(result).toMatch(/ :(red-sus|rish_sus):/)
14+
})
15+
})
16+
17+
describe('getRelativeHumanReadableAge', () => {
18+
it('should return "in the last hour" for less than 1 hour', () => {
19+
expect(getRelativeHumanReadableAge(0.5)).toBe('in the last hour')
20+
})
21+
22+
it('should return hours with pluralization', () => {
23+
expect(getRelativeHumanReadableAge(1)).toBe('1 hour ago')
24+
expect(getRelativeHumanReadableAge(2)).toBe('2 hours ago')
25+
})
26+
27+
it('should return days with pluralization', () => {
28+
expect(getRelativeHumanReadableAge(24)).toBe('1 day ago')
29+
expect(getRelativeHumanReadableAge(48)).toBe('2 days ago')
30+
})
31+
32+
it('should handle without "ago" suffix', () => {
33+
expect(getRelativeHumanReadableAge(1, false)).toBe('1 hour')
34+
expect(getRelativeHumanReadableAge(2, false)).toBe('2 hours')
35+
expect(getRelativeHumanReadableAge(24, false)).toBe('1 day')
36+
expect(getRelativeHumanReadableAge(48, false)).toBe('2 days')
37+
})
38+
})
39+
40+
describe('haveOrHas', () => {
41+
it('should return "has" for singular', () => {
42+
expect(haveOrHas(1)).toBe('has')
43+
})
44+
45+
it('should return "have" for plural', () => {
46+
expect(haveOrHas(0)).toBe('have')
47+
expect(haveOrHas(2)).toBe('have')
48+
expect(haveOrHas(10)).toBe('have')
49+
})
50+
})
51+
})

tests/utils/slack/blocks.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { getContextMarkdownBlock, getEmojiBlocks, getFirstBlocks, getLastBlocks } from '../../../src/utils/slack/blocks.js'
2+
3+
describe('Utils: Slack', () => {
4+
describe('Blocks', () => {
5+
describe('getContextMarkdownBlock', () => {
6+
it('should create a context block with markdown text', () => {
7+
const result = getContextMarkdownBlock('test markdown')
8+
expect(result).toHaveLength(1)
9+
expect(result[0]).toEqual({
10+
elements: [
11+
{
12+
text: 'test markdown',
13+
type: 'mrkdwn',
14+
},
15+
],
16+
type: 'context',
17+
})
18+
})
19+
20+
it('should include indentation when specified', () => {
21+
const result = getContextMarkdownBlock('test markdown', true)
22+
expect(result).toHaveLength(1)
23+
expect(result[0]).toEqual({
24+
elements: [
25+
{
26+
text: ' ',
27+
type: 'plain_text',
28+
},
29+
{
30+
text: 'test markdown',
31+
type: 'mrkdwn',
32+
},
33+
],
34+
type: 'context',
35+
})
36+
})
37+
})
38+
39+
describe('getEmojiBlocks', () => {
40+
it('should return empty array when no emoji name provided', () => {
41+
expect(getEmojiBlocks()).toEqual([])
42+
})
43+
44+
it('should create rich text section blocks', () => {
45+
const result = getEmojiBlocks('smile')
46+
expect(result).toEqual([
47+
{
48+
name: 'smile',
49+
type: 'emoji',
50+
},
51+
{
52+
text: ' ',
53+
type: 'text',
54+
},
55+
])
56+
})
57+
58+
it('should create context blocks', () => {
59+
const result = getEmojiBlocks('smile', 'context')
60+
expect(result).toEqual([
61+
{
62+
emoji: true,
63+
text: ':smile:',
64+
type: 'plain_text',
65+
},
66+
])
67+
})
68+
})
69+
70+
describe('getFirstBlocks', () => {
71+
it('should create header and action blocks', () => {
72+
const result = getFirstBlocks(['org1'], 'Test Header')
73+
expect(result).toHaveLength(2)
74+
expect(result[0]).toEqual({
75+
text: {
76+
emoji: true,
77+
text: 'Test Header',
78+
type: 'plain_text',
79+
},
80+
type: 'header',
81+
})
82+
expect(result[1]).toEqual({
83+
elements: [
84+
{
85+
text: {
86+
emoji: true,
87+
text: 'Org1 Org',
88+
type: 'plain_text',
89+
},
90+
type: 'button',
91+
url: 'https://github.com/org1',
92+
},
93+
{
94+
text: {
95+
emoji: true,
96+
text: 'Org1 PRs',
97+
type: 'plain_text',
98+
},
99+
type: 'button',
100+
url: 'https://github.com/pulls?q=is%3Aopen+is%3Apr+archived%3Afalse+draft%3Afalse+user%3Aorg1',
101+
},
102+
],
103+
type: 'actions',
104+
})
105+
})
106+
107+
it('should include sub-header text when provided', () => {
108+
const result = getFirstBlocks(['org1'], 'Test Header', 'Sub-header text')
109+
expect(result).toHaveLength(3)
110+
expect(result[1]).toEqual({
111+
elements: [
112+
{
113+
text: 'Sub-header text',
114+
type: 'mrkdwn',
115+
},
116+
],
117+
type: 'context',
118+
})
119+
})
120+
})
121+
122+
describe('getLastBlocks', () => {
123+
it('should create context block with footer text', () => {
124+
const result = getLastBlocks('footer text')
125+
expect(result).toHaveLength(1)
126+
expect(result[0]).toEqual({
127+
elements: [
128+
{
129+
text: 'footer text',
130+
type: 'mrkdwn',
131+
},
132+
],
133+
type: 'context',
134+
})
135+
})
136+
})
137+
})
138+
})

tsconfig.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@
1313
"allowArbitraryExtensions": true,
1414
"verbatimModuleSyntax": false,
1515
"resolveJsonModule": true,
16+
"isolatedModules": true
1617
},
1718
"include": [
1819
"src/**/*.ts",
19-
"src/**/*.tsx"
20+
"src/**/*.tsx",
21+
"tests/**/*.ts"
2022
],
2123
"exclude": ["node_modules", ".vscode"],
2224
"ts-node": {

0 commit comments

Comments
 (0)