@codeclimbers/server 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/src/app.module.d.ts +4 -0
- package/dist/src/app.module.js +49 -0
- package/dist/src/app.module.js.map +1 -0
- package/dist/src/assets/startup.plist.d.ts +1 -0
- package/dist/src/assets/startup.plist.js +41 -0
- package/dist/src/assets/startup.plist.js.map +1 -0
- package/dist/src/common/infrastructure/http/controllers/health.controller.d.ts +5 -0
- package/dist/src/common/infrastructure/http/controllers/health.controller.js +29 -0
- package/dist/src/common/infrastructure/http/controllers/health.controller.js.map +1 -0
- package/dist/src/common/infrastructure/http/middleware/requestlogger.middleware.d.ts +5 -0
- package/dist/src/common/infrastructure/http/middleware/requestlogger.middleware.js +27 -0
- package/dist/src/common/infrastructure/http/middleware/requestlogger.middleware.js.map +1 -0
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.js +44 -0
- package/dist/src/main.js.map +1 -0
- package/dist/src/sentry.d.ts +1 -0
- package/dist/src/sentry.js +13 -0
- package/dist/src/sentry.js.map +1 -0
- package/dist/src/v1/activities/activities.service.d.ts +21 -0
- package/dist/src/v1/activities/activities.service.js +174 -0
- package/dist/src/v1/activities/activities.service.js.map +1 -0
- package/dist/src/v1/activities/pulse.controller.d.ts +19 -0
- package/dist/src/v1/activities/pulse.controller.js +92 -0
- package/dist/src/v1/activities/pulse.controller.js.map +1 -0
- package/dist/src/v1/activities/wakatimeProxy.controller.d.ts +13 -0
- package/dist/src/v1/activities/wakatimeProxy.controller.js +64 -0
- package/dist/src/v1/activities/wakatimeProxy.controller.js.map +1 -0
- package/dist/src/v1/database/__tests__/knex.test.d.ts +1 -0
- package/dist/src/v1/database/__tests__/knex.test.js +18 -0
- package/dist/src/v1/database/__tests__/knex.test.js.map +1 -0
- package/dist/src/v1/database/__tests__/pulse.repo.test.d.ts +1 -0
- package/dist/src/v1/database/__tests__/pulse.repo.test.js +141 -0
- package/dist/src/v1/database/__tests__/pulse.repo.test.js.map +1 -0
- package/dist/src/v1/database/knex.d.ts +5 -0
- package/dist/src/v1/database/knex.js +87 -0
- package/dist/src/v1/database/knex.js.map +1 -0
- package/dist/src/v1/database/migrations.d.ts +1 -0
- package/dist/src/v1/database/migrations.js +16 -0
- package/dist/src/v1/database/migrations.js.map +1 -0
- package/dist/src/v1/database/pulse.repo.d.ts +17 -0
- package/dist/src/v1/database/pulse.repo.js +115 -0
- package/dist/src/v1/database/pulse.repo.js.map +1 -0
- package/dist/src/v1/database/queries/getCategoryTimeOverview.sql +6 -0
- package/dist/src/v1/database/queries/getLongestDayInRangeMinutes.sql +11 -0
- package/dist/src/v1/database/queries/getStatusBarDetails.sql +42 -0
- package/dist/src/v1/dtos/createWakatimePulse.dto.d.ts +19 -0
- package/dist/src/v1/dtos/createWakatimePulse.dto.js +97 -0
- package/dist/src/v1/dtos/createWakatimePulse.dto.js.map +1 -0
- package/dist/src/v1/dtos/getCategoryTimeOverview.dto.d.ts +4 -0
- package/dist/src/v1/dtos/getCategoryTimeOverview.dto.js +25 -0
- package/dist/src/v1/dtos/getCategoryTimeOverview.dto.js.map +1 -0
- package/dist/src/v1/dtos/getWeekOverview.dto.d.ts +3 -0
- package/dist/src/v1/dtos/getWeekOverview.dto.js +21 -0
- package/dist/src/v1/dtos/getWeekOverview.dto.js.map +1 -0
- package/dist/src/v1/startup/darwinStartup.service.d.ts +8 -0
- package/dist/src/v1/startup/darwinStartup.service.js +114 -0
- package/dist/src/v1/startup/darwinStartup.service.js.map +1 -0
- package/dist/src/v1/startup/linuxStartup.service.d.ts +8 -0
- package/dist/src/v1/startup/linuxStartup.service.js +104 -0
- package/dist/src/v1/startup/linuxStartup.service.js.map +1 -0
- package/dist/src/v1/startup/startup.controller.d.ts +8 -0
- package/dist/src/v1/startup/startup.controller.js +46 -0
- package/dist/src/v1/startup/startup.controller.js.map +1 -0
- package/dist/src/v1/startup/startup.util.d.ts +5 -0
- package/dist/src/v1/startup/startup.util.js +19 -0
- package/dist/src/v1/startup/startup.util.js.map +1 -0
- package/dist/src/v1/startup/startupService.factory.d.ts +9 -0
- package/dist/src/v1/startup/startupService.factory.js +41 -0
- package/dist/src/v1/startup/startupService.factory.js.map +1 -0
- package/dist/src/v1/startup/unsupportedStartup.service.d.ts +6 -0
- package/dist/src/v1/startup/unsupportedStartup.service.js +29 -0
- package/dist/src/v1/startup/unsupportedStartup.service.js.map +1 -0
- package/dist/src/v1/v1.module.d.ts +2 -0
- package/dist/src/v1/v1.module.js +41 -0
- package/dist/src/v1/v1.module.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/dist/utils/__tests__/activites.util.test.d.ts +1 -0
- package/dist/utils/__tests__/activites.util.test.js +18 -0
- package/dist/utils/__tests__/activites.util.test.js.map +1 -0
- package/dist/utils/__tests__/helpers.util.test.d.ts +1 -0
- package/dist/utils/__tests__/helpers.util.test.js +120 -0
- package/dist/utils/__tests__/helpers.util.test.js.map +1 -0
- package/dist/utils/__tests__/wakatime.util.test.d.ts +1 -0
- package/dist/utils/__tests__/wakatime.util.test.js +210 -0
- package/dist/utils/__tests__/wakatime.util.test.js.map +1 -0
- package/dist/utils/activities.util.d.ts +15 -0
- package/dist/utils/activities.util.js +147 -0
- package/dist/utils/activities.util.js.map +1 -0
- package/dist/utils/allExceptions.filter.d.ts +7 -0
- package/dist/utils/allExceptions.filter.js +39 -0
- package/dist/utils/allExceptions.filter.js.map +1 -0
- package/dist/utils/codeClimberErrors.d.ts +16 -0
- package/dist/utils/codeClimberErrors.js +27 -0
- package/dist/utils/codeClimberErrors.js.map +1 -0
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/constants.js +5 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/environment.util.d.ts +1 -0
- package/dist/utils/environment.util.js +6 -0
- package/dist/utils/environment.util.js.map +1 -0
- package/dist/utils/helpers.util.d.ts +7 -0
- package/dist/utils/helpers.util.js +116 -0
- package/dist/utils/helpers.util.js.map +1 -0
- package/dist/utils/node.util.d.ts +7 -0
- package/dist/utils/node.util.js +26 -0
- package/dist/utils/node.util.js.map +1 -0
- package/dist/utils/sql.util.d.ts +1 -0
- package/dist/utils/sql.util.js +11 -0
- package/dist/utils/sql.util.js.map +1 -0
- package/dist/utils/sqlReader.util.d.ts +5 -0
- package/dist/utils/sqlReader.util.js +34 -0
- package/dist/utils/sqlReader.util.js.map +1 -0
- package/dist/utils/wakatime.util.d.ts +6 -0
- package/dist/utils/wakatime.util.js +63 -0
- package/dist/utils/wakatime.util.js.map +1 -0
- package/index.ts +5 -0
- package/jest.config.js +8 -0
- package/knexfile.js +14 -0
- package/nest-cli.json +15 -0
- package/package.json +77 -0
- package/src/app.module.ts +37 -0
- package/src/assets/startup.plist.ts +36 -0
- package/src/common/infrastructure/http/controllers/health.controller.ts +9 -0
- package/src/common/infrastructure/http/middleware/requestlogger.middleware.ts +22 -0
- package/src/main.ts +51 -0
- package/src/sentry.ts +14 -0
- package/src/types/activities.api.d.ts +18 -0
- package/src/types/time.api.d.ts +23 -0
- package/src/types/utils.d.ts +8 -0
- package/src/types/wakatimeProxy.api.d.ts +55 -0
- package/src/v1/activities/activities.service.ts +213 -0
- package/src/v1/activities/pulse.controller.ts +68 -0
- package/src/v1/activities/wakatimeProxy.controller.ts +33 -0
- package/src/v1/database/__tests__/knex.test.ts +18 -0
- package/src/v1/database/__tests__/pulse.repo.test.ts +209 -0
- package/src/v1/database/knex.ts +107 -0
- package/src/v1/database/migrations.ts +13 -0
- package/src/v1/database/models/pulse.d.ts +24 -0
- package/src/v1/database/pulse.repo.ts +132 -0
- package/src/v1/database/queries/getCategoryTimeOverview.sql +6 -0
- package/src/v1/database/queries/getLongestDayInRangeMinutes.sql +11 -0
- package/src/v1/database/queries/getStatusBarDetails.sql +42 -0
- package/src/v1/dtos/createWakatimePulse.dto.ts +66 -0
- package/src/v1/dtos/getCategoryTimeOverview.dto.ts +9 -0
- package/src/v1/dtos/getWeekOverview.dto.ts +6 -0
- package/src/v1/startup/darwinStartup.service.ts +114 -0
- package/src/v1/startup/linuxStartup.service.ts +101 -0
- package/src/v1/startup/startup.controller.ts +23 -0
- package/src/v1/startup/startup.util.ts +21 -0
- package/src/v1/startup/startupService.factory.ts +28 -0
- package/src/v1/startup/unsupportedStartup.service.ts +21 -0
- package/src/v1/v1.module.ts +28 -0
- package/test/app.e2e-spec.ts +24 -0
- package/test/jest-e2e.json +9 -0
- package/test/jest.globalSetup.js +15 -0
- package/test/jest.globalTeardown.js +8 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +22 -0
- package/utils/__tests__/activites.util.test.ts +18 -0
- package/utils/__tests__/helpers.util.test.ts +155 -0
- package/utils/__tests__/wakatime.util.test.ts +222 -0
- package/utils/activities.util.ts +193 -0
- package/utils/allExceptions.filter.ts +43 -0
- package/utils/codeClimberErrors.ts +32 -0
- package/utils/constants.ts +1 -0
- package/utils/environment.util.ts +1 -0
- package/utils/helpers.util.ts +131 -0
- package/utils/node.util.ts +29 -0
- package/utils/sql.util.ts +13 -0
- package/utils/sqlReader.util.ts +49 -0
- package/utils/wakatime.util.ts +78 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common'
|
|
2
|
+
import { DarwinStartupService } from './darwinStartup.service'
|
|
3
|
+
import { UnsupportedStartupService } from './unsupportedStartup.service'
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class StartupServiceFactory {
|
|
7
|
+
constructor(
|
|
8
|
+
private readonly darwinStartupService: DarwinStartupService,
|
|
9
|
+
private readonly unsupportedStartupService: UnsupportedStartupService,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
getStartupService(): CodeClimbers.StartupService {
|
|
13
|
+
const os = process.platform
|
|
14
|
+
switch (os) {
|
|
15
|
+
case 'darwin':
|
|
16
|
+
return this.darwinStartupService
|
|
17
|
+
default:
|
|
18
|
+
return this.unsupportedStartupService
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static buildStartupService(): CodeClimbers.StartupService {
|
|
23
|
+
return new StartupServiceFactory(
|
|
24
|
+
new DarwinStartupService(),
|
|
25
|
+
new UnsupportedStartupService(),
|
|
26
|
+
).getStartupService()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common'
|
|
2
|
+
|
|
3
|
+
@Injectable()
|
|
4
|
+
export class UnsupportedStartupService implements CodeClimbers.StartupService {
|
|
5
|
+
async enableStartup() {
|
|
6
|
+
Logger.error(`Unsupported operating system: ${process.platform}`)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async disableStartup() {
|
|
10
|
+
Logger.error(`Unsupported operating system: ${process.platform}`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// cleanly separate implementation code for each environment. If I'm working on windows, I see all the implementation around startup
|
|
14
|
+
async launchAndEnableStartup() {
|
|
15
|
+
Logger.error(`Unsupported operating system: ${process.platform}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async closeAndDisableStartup() {
|
|
19
|
+
Logger.error(`Unsupported operating system: ${process.platform}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common'
|
|
2
|
+
import { HealthController } from '../common/infrastructure/http/controllers/health.controller'
|
|
3
|
+
import { StartupController } from './startup/startup.controller'
|
|
4
|
+
import { ActivitiesService } from './activities/activities.service'
|
|
5
|
+
import { PulseController } from './activities/pulse.controller'
|
|
6
|
+
import { WakatimeController } from './activities/wakatimeProxy.controller'
|
|
7
|
+
import { PulseRepo } from './database/pulse.repo'
|
|
8
|
+
import { DarwinStartupService } from './startup/darwinStartup.service'
|
|
9
|
+
import { StartupServiceFactory } from './startup/startupService.factory'
|
|
10
|
+
import { UnsupportedStartupService } from './startup/unsupportedStartup.service'
|
|
11
|
+
|
|
12
|
+
@Module({
|
|
13
|
+
imports: [],
|
|
14
|
+
controllers: [
|
|
15
|
+
HealthController,
|
|
16
|
+
PulseController,
|
|
17
|
+
WakatimeController,
|
|
18
|
+
StartupController,
|
|
19
|
+
],
|
|
20
|
+
providers: [
|
|
21
|
+
ActivitiesService,
|
|
22
|
+
StartupServiceFactory,
|
|
23
|
+
PulseRepo,
|
|
24
|
+
UnsupportedStartupService,
|
|
25
|
+
DarwinStartupService,
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
export class V1Module {}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Test, TestingModule } from '@nestjs/testing'
|
|
2
|
+
import { INestApplication } from '@nestjs/common'
|
|
3
|
+
import * as request from 'supertest'
|
|
4
|
+
import { AppModule } from './../src/app.module'
|
|
5
|
+
|
|
6
|
+
describe('AppController (e2e)', () => {
|
|
7
|
+
let app: INestApplication
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
11
|
+
imports: [AppModule],
|
|
12
|
+
}).compile()
|
|
13
|
+
|
|
14
|
+
app = moduleFixture.createNestApplication()
|
|
15
|
+
await app.init()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('/ (GET)', () => {
|
|
19
|
+
return request(app.getHttpServer())
|
|
20
|
+
.get('/')
|
|
21
|
+
.expect(200)
|
|
22
|
+
.expect('Hello World!')
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { knex, SQL_LITE_TEST_FILE } = require('../src/v1/database/knex')
|
|
2
|
+
|
|
3
|
+
module.exports = async () => {
|
|
4
|
+
if (knex.client.config.connection.filename !== SQL_LITE_TEST_FILE) {
|
|
5
|
+
throw new Error('You are not using the test file')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
console.log('\n======== SETUP - DROP ========')
|
|
9
|
+
await knex.migrate.rollback({}, true)
|
|
10
|
+
|
|
11
|
+
console.log('======== SETUP - MIGRATION ========')
|
|
12
|
+
await knex.migrate.latest()
|
|
13
|
+
console.log('======== SETUP - SEED ========')
|
|
14
|
+
await knex.seed.run()
|
|
15
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"module": "CommonJS",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"removeComments": true,
|
|
8
|
+
"emitDecoratorMetadata": true,
|
|
9
|
+
"experimentalDecorators": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"target": "ES2021",
|
|
12
|
+
"sourceMap": true,
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"incremental": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strictNullChecks": false,
|
|
17
|
+
"noImplicitAny": false,
|
|
18
|
+
"strictBindCallApply": false,
|
|
19
|
+
"forceConsistentCasingInFileNames": false,
|
|
20
|
+
"noFallthroughCasesInSwitch": false,
|
|
21
|
+
},
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import activitiesUtil from '../activities.util'
|
|
2
|
+
|
|
3
|
+
describe('getSourceFromUserAgent', () => {
|
|
4
|
+
it(`should return 'vscode' as source of userAgents`, () => {
|
|
5
|
+
const userAgents = [
|
|
6
|
+
'wakatime/v1.98.1 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-climbers/0.0.0',
|
|
7
|
+
'wakatime/v1.98.1 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-wakatime/24.6.0',
|
|
8
|
+
'wakatime/v1.98.3 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-climbers/0.0.0',
|
|
9
|
+
'wakatime/v1.98.3 (windows-10.0.22631.3880-unknown) go1.22.5 vscode/1.91.1 vscode-wakatime/24.6.0',
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const result = userAgents.map((userAgent) => {
|
|
13
|
+
return activitiesUtil.getSourceFromUserAgent(userAgent)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(result).toEqual(['vscode', 'vscode', 'vscode', 'vscode'])
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import {
|
|
3
|
+
forOwn,
|
|
4
|
+
isPlainObject,
|
|
5
|
+
snakeCase,
|
|
6
|
+
camelCase,
|
|
7
|
+
maxBy,
|
|
8
|
+
minBy,
|
|
9
|
+
groupBy,
|
|
10
|
+
} from '../helpers.util'
|
|
11
|
+
|
|
12
|
+
describe('forOwn', () => {
|
|
13
|
+
it('should iterate over own enumerable properties', () => {
|
|
14
|
+
const obj = { a: 1, b: 2, c: 3 }
|
|
15
|
+
const result: [string, number][] = []
|
|
16
|
+
|
|
17
|
+
forOwn(obj, (value, key) => {
|
|
18
|
+
result.push([key, value])
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
expect(result).toEqual([
|
|
22
|
+
['a', 1],
|
|
23
|
+
['b', 2],
|
|
24
|
+
['c', 3],
|
|
25
|
+
])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('should handle empty objects', () => {
|
|
29
|
+
const obj = {}
|
|
30
|
+
let count = 0
|
|
31
|
+
|
|
32
|
+
forOwn(obj, () => {
|
|
33
|
+
count++
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect(count).toBe(0)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('isPlainObject', () => {
|
|
41
|
+
it('should return true for plain objects', () => {
|
|
42
|
+
expect(isPlainObject({})).toBe(true)
|
|
43
|
+
expect(isPlainObject({ a: 1 })).toBe(true)
|
|
44
|
+
expect(isPlainObject(Object.create(null))).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should return false for non-plain objects', () => {
|
|
48
|
+
expect(isPlainObject(null)).toBe(false)
|
|
49
|
+
expect(isPlainObject(undefined)).toBe(false)
|
|
50
|
+
expect(isPlainObject(123)).toBe(false)
|
|
51
|
+
expect(isPlainObject('string')).toBe(false)
|
|
52
|
+
expect(isPlainObject([])).toBe(false)
|
|
53
|
+
expect(isPlainObject(new Date())).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('snakeCase', () => {
|
|
58
|
+
it('should convert strings to snake case', () => {
|
|
59
|
+
expect(snakeCase('hello world')).toBe('hello_world')
|
|
60
|
+
expect(snakeCase('helloWorld')).toBe('hello_world')
|
|
61
|
+
expect(snakeCase('HelloWorld')).toBe('hello_world')
|
|
62
|
+
expect(snakeCase('hello-world')).toBe('hello_world')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should handle non-string inputs', () => {
|
|
66
|
+
expect(snakeCase(null as any)).toBe('')
|
|
67
|
+
expect(snakeCase(123 as any)).toBe('')
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('camelCase', () => {
|
|
72
|
+
it('should convert strings to camel case', () => {
|
|
73
|
+
expect(camelCase('hello world')).toBe('helloWorld')
|
|
74
|
+
expect(camelCase('hello-world')).toBe('helloWorld')
|
|
75
|
+
expect(camelCase('hello_world')).toBe('helloWorld')
|
|
76
|
+
expect(camelCase('Hello World')).toBe('helloWorld')
|
|
77
|
+
expect(camelCase('HELLO WORLD')).toBe('helloWorld')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should handle non-string inputs', () => {
|
|
81
|
+
expect(camelCase(null as any)).toBe('')
|
|
82
|
+
expect(camelCase(123 as any)).toBe('')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('maxBy', () => {
|
|
87
|
+
it('should return the maximum value by the given key', () => {
|
|
88
|
+
const arr = [
|
|
89
|
+
{ a: 1, b: 3 },
|
|
90
|
+
{ a: 2, b: 1 },
|
|
91
|
+
{ a: 3, b: 2 },
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
// row 3
|
|
95
|
+
// row 1Î
|
|
96
|
+
expect(maxBy(arr, (x) => x.a)).toEqual({ a: 3, b: 2 })
|
|
97
|
+
expect(maxBy(arr, (x) => x.b)).toEqual({ a: 1, b: 3 })
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should return undefined for empty arrays', () => {
|
|
101
|
+
expect(
|
|
102
|
+
maxBy([], () => {
|
|
103
|
+
return
|
|
104
|
+
}),
|
|
105
|
+
).toBeUndefined()
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('minBy', () => {
|
|
110
|
+
it('should return the minimum value by the given key', () => {
|
|
111
|
+
const arr = [
|
|
112
|
+
{ a: 1, b: 2 },
|
|
113
|
+
{ a: 2, b: 1 },
|
|
114
|
+
{ a: 3, b: 3 },
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
expect(minBy(arr, (x) => x.a)).toEqual({ a: 1, b: 2 })
|
|
118
|
+
expect(minBy(arr, (x) => x.b)).toEqual({ a: 2, b: 1 })
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should return undefined for empty arrays', () => {
|
|
122
|
+
expect(
|
|
123
|
+
minBy([], () => {
|
|
124
|
+
return
|
|
125
|
+
}),
|
|
126
|
+
).toBeUndefined()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
describe('groupBy', () => {
|
|
131
|
+
it('should group items by the given key', () => {
|
|
132
|
+
const arr = [
|
|
133
|
+
{ a: 1, b: 2 },
|
|
134
|
+
{ a: 2, b: 1 },
|
|
135
|
+
{ a: 1, b: 3 },
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
const result = groupBy(arr, 'a' as any)
|
|
139
|
+
|
|
140
|
+
expect(result).toEqual({
|
|
141
|
+
1: [
|
|
142
|
+
{ a: 1, b: 2 },
|
|
143
|
+
{ a: 1, b: 3 },
|
|
144
|
+
],
|
|
145
|
+
2: [{ a: 2, b: 1 }],
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should handle empty arrays', () => {
|
|
150
|
+
const arr: any[] = []
|
|
151
|
+
const result = groupBy(arr, 'a' as any)
|
|
152
|
+
|
|
153
|
+
expect(result).toEqual({})
|
|
154
|
+
})
|
|
155
|
+
})
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { IniConfig, parseIni, stringifyIni } from '../wakatime.util'
|
|
2
|
+
|
|
3
|
+
describe('parseIni', () => {
|
|
4
|
+
it('should parse a valid INI string with one section', () => {
|
|
5
|
+
const input = `
|
|
6
|
+
[section1]
|
|
7
|
+
key1 = value1
|
|
8
|
+
key2 = value2
|
|
9
|
+
`
|
|
10
|
+
const expected: IniConfig = {
|
|
11
|
+
section1: {
|
|
12
|
+
key1: 'value1',
|
|
13
|
+
key2: 'value2',
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
expect(parseIni(input)).toEqual(expected)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should parse a valid INI string with multiple sections', () => {
|
|
20
|
+
const input = `
|
|
21
|
+
[section1]
|
|
22
|
+
key1 = value1
|
|
23
|
+
key2 = value2
|
|
24
|
+
|
|
25
|
+
[section2]
|
|
26
|
+
key3 = value3
|
|
27
|
+
key4 = value4
|
|
28
|
+
`
|
|
29
|
+
const expected: IniConfig = {
|
|
30
|
+
section1: {
|
|
31
|
+
key1: 'value1',
|
|
32
|
+
key2: 'value2',
|
|
33
|
+
},
|
|
34
|
+
section2: {
|
|
35
|
+
key3: 'value3',
|
|
36
|
+
key4: 'value4',
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
expect(parseIni(input)).toEqual(expected)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should handle empty input', () => {
|
|
43
|
+
const input = ''
|
|
44
|
+
const expected: IniConfig = {}
|
|
45
|
+
expect(parseIni(input)).toEqual(expected)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('should handle input with only whitespace', () => {
|
|
49
|
+
const input = ' \n \t '
|
|
50
|
+
const expected: IniConfig = {}
|
|
51
|
+
expect(parseIni(input)).toEqual(expected)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('should ignore lines without a section', () => {
|
|
55
|
+
const input = `
|
|
56
|
+
key1 = value1
|
|
57
|
+
[section1]
|
|
58
|
+
key2 = value2
|
|
59
|
+
`
|
|
60
|
+
const expected: IniConfig = {
|
|
61
|
+
section1: {
|
|
62
|
+
key2: 'value2',
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
expect(parseIni(input)).toEqual(expected)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should handle malformed lines within a section', () => {
|
|
69
|
+
const input = `
|
|
70
|
+
[section1]
|
|
71
|
+
key1 = value1
|
|
72
|
+
malformed line
|
|
73
|
+
key2 = value2
|
|
74
|
+
`
|
|
75
|
+
const expected: IniConfig = {
|
|
76
|
+
section1: {
|
|
77
|
+
key1: 'value1',
|
|
78
|
+
key2: 'value2',
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
expect(parseIni(input)).toEqual(expected)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should handle keys without values', () => {
|
|
85
|
+
const input = `
|
|
86
|
+
[section1]
|
|
87
|
+
key1 = value1
|
|
88
|
+
key2 =
|
|
89
|
+
key3 = value3
|
|
90
|
+
`
|
|
91
|
+
const expected: IniConfig = {
|
|
92
|
+
section1: {
|
|
93
|
+
key1: 'value1',
|
|
94
|
+
key3: 'value3',
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
expect(parseIni(input)).toEqual(expected)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should handle section names with spaces', () => {
|
|
101
|
+
const input = `
|
|
102
|
+
[Section One]
|
|
103
|
+
key1 = value1
|
|
104
|
+
[Section Two]
|
|
105
|
+
key2 = value2
|
|
106
|
+
`
|
|
107
|
+
const expected: IniConfig = {
|
|
108
|
+
'Section One': {
|
|
109
|
+
key1: 'value1',
|
|
110
|
+
},
|
|
111
|
+
'Section Two': {
|
|
112
|
+
key2: 'value2',
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
expect(parseIni(input)).toEqual(expected)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('stringifyIni', () => {
|
|
120
|
+
it('should return an empty string for empty input', () => {
|
|
121
|
+
const input: IniConfig = {}
|
|
122
|
+
const expected = '\n'
|
|
123
|
+
expect(stringifyIni(input)).toBe(expected)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('should correctly stringify a single section with multiple entries', () => {
|
|
127
|
+
const input: IniConfig = {
|
|
128
|
+
section1: {
|
|
129
|
+
key1: 'value1',
|
|
130
|
+
key2: 'value2',
|
|
131
|
+
key3: 'value3',
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
const expected = `[section1]
|
|
135
|
+
key1 = value1
|
|
136
|
+
key2 = value2
|
|
137
|
+
key3 = value3\n`
|
|
138
|
+
expect(stringifyIni(input)).toBe(expected)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('should correctly stringify multiple sections with multiple entries', () => {
|
|
142
|
+
const input: IniConfig = {
|
|
143
|
+
section1: {
|
|
144
|
+
key1: 'value1',
|
|
145
|
+
key2: 'value2',
|
|
146
|
+
},
|
|
147
|
+
section2: {
|
|
148
|
+
key3: 'value3',
|
|
149
|
+
key4: 'value4',
|
|
150
|
+
},
|
|
151
|
+
}
|
|
152
|
+
const expected = `[section1]
|
|
153
|
+
key1 = value1
|
|
154
|
+
key2 = value2
|
|
155
|
+
|
|
156
|
+
[section2]
|
|
157
|
+
key3 = value3
|
|
158
|
+
key4 = value4\n`
|
|
159
|
+
expect(stringifyIni(input)).toBe(expected)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should handle sections with no entries', () => {
|
|
163
|
+
const input: IniConfig = {
|
|
164
|
+
section1: {},
|
|
165
|
+
section2: {
|
|
166
|
+
key1: 'value1',
|
|
167
|
+
},
|
|
168
|
+
section3: {},
|
|
169
|
+
}
|
|
170
|
+
const expected = `[section1]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
[section2]
|
|
174
|
+
key1 = value1
|
|
175
|
+
|
|
176
|
+
[section3]
|
|
177
|
+
\n`
|
|
178
|
+
expect(stringifyIni(input)).toBe(expected)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('should handle a mix of sections with and without entries', () => {
|
|
182
|
+
const input: IniConfig = {
|
|
183
|
+
section1: {
|
|
184
|
+
key1: 'value1',
|
|
185
|
+
},
|
|
186
|
+
section2: {},
|
|
187
|
+
section3: {
|
|
188
|
+
key2: 'value2',
|
|
189
|
+
key3: 'value3',
|
|
190
|
+
},
|
|
191
|
+
section4: {},
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const expected = `[section1]
|
|
195
|
+
key1 = value1
|
|
196
|
+
|
|
197
|
+
[section2]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
[section3]
|
|
201
|
+
key2 = value2
|
|
202
|
+
key3 = value3
|
|
203
|
+
|
|
204
|
+
[section4]
|
|
205
|
+
\n`
|
|
206
|
+
console.log(stringifyIni(input))
|
|
207
|
+
expect(stringifyIni(input)).toBe(expected)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('should handle values containing spaces', () => {
|
|
211
|
+
const input: IniConfig = {
|
|
212
|
+
section1: {
|
|
213
|
+
key1: 'value with spaces',
|
|
214
|
+
key2: 'another value with spaces',
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
const expected = `[section1]
|
|
218
|
+
key1 = value with spaces
|
|
219
|
+
key2 = another value with spaces\n`
|
|
220
|
+
expect(stringifyIni(input)).toBe(expected)
|
|
221
|
+
})
|
|
222
|
+
})
|