Multiple exercises were added to the TypeScript track in this commit.

This commit is contained in:
2025-04-16 21:10:33 +02:00
parent e0d01e1c8d
commit 83ec49cb5b
72 changed files with 81869 additions and 0 deletions
@@ -0,0 +1,32 @@
{
"authors": [
"bward"
],
"contributors": [
"masters3d",
"Roshanjossey",
"SleeplessByte"
],
"files": {
"solution": [
"atbash-cipher.ts"
],
"test": [
"atbash-cipher.test.ts"
],
"example": [
".meta/proof.ci.ts"
]
},
"blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.",
"custom": {
"version.tests.compatibility": "jest-29",
"flag.tests.task-per-describe": false,
"flag.tests.may-run-long": false,
"flag.tests.includes-optional": false,
"flag.tests.jest": true,
"flag.tests.tstyche": false
},
"source": "Wikipedia",
"source_url": "https://en.wikipedia.org/wiki/Atbash"
}
@@ -0,0 +1 @@
{"track":"typescript","exercise":"atbash-cipher","id":"367773a215d741fb87311fe2e2a59afe","url":"https://exercism.org/tracks/typescript/exercises/atbash-cipher","handle":"briemens","is_requester":true,"auto_approve":false}
+16691
View File
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"cSpell.words": ["exercism"],
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
}
}
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
compressionLevel: mixed
enableGlobalCache: true
+50
View File
@@ -0,0 +1,50 @@
# Help
## Running the tests
Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup.
Execute the tests with:
```bash
$ corepack yarn test
```
## Skipped tests
In the test suites all tests but the first have been skipped.
Once you get a test passing, you can enable the next one by changing `xit` to `it`.
Additionally tests may be grouped using `xdescribe`.
Enable the group by changing that to `describe`.
Finally, some exercises may have optional tests `it.skip`.
Remove `.skip` to execute the optional test.
## Submitting your solution
You can submit your solution using the `exercism submit atbash-cipher.ts` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript)
- The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html)
- [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf)
- [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference)
- [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit.
- [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
+48
View File
@@ -0,0 +1,48 @@
# Atbash Cipher
Welcome to Atbash Cipher on Exercism's TypeScript Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.
The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards.
The first letter is replaced with the last letter, the second with the second-last, and so on.
An Atbash cipher for the Latin alphabet would be as follows:
```text
Plain: abcdefghijklmnopqrstuvwxyz
Cipher: zyxwvutsrqponmlkjihgfedcba
```
It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher.
However, this may not have been an issue in the cipher's time.
Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded.
This is to make it harder to guess things based on word boundaries.
All text will be encoded as lowercase letters.
## Examples
- Encoding `test` gives `gvhg`
- Encoding `x123 yes` gives `c123b vh`
- Decoding `gvhg` gives `test`
- Decoding `gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt` gives `thequickbrownfoxjumpsoverthelazydog`
## Source
### Created by
- @bward
### Contributed to by
- @masters3d
- @Roshanjossey
- @SleeplessByte
### Based on
Wikipedia - https://en.wikipedia.org/wiki/Atbash
@@ -0,0 +1,68 @@
import { describe, expect, it, xdescribe } from '@jest/globals'
import { decode, encode } from './atbash-cipher.ts'
describe('AtbashCipher', () => {
describe('encoding', () => {
it('encode yes', () => {
const cipherText = encode('yes')
expect(cipherText).toEqual('bvh')
})
it('encode no', () => {
const cipherText = encode('no')
expect(cipherText).toEqual('ml')
})
it('encode OMG', () => {
const cipherText = encode('OMG')
expect(cipherText).toEqual('lnt')
})
it('encode spaces', () => {
const cipherText = encode('O M G')
expect(cipherText).toEqual('lnt')
})
it('encode mindblowingly', () => {
const cipherText = encode('mindblowingly')
expect(cipherText).toEqual('nrmwy oldrm tob')
})
it('encode numbers', () => {
const cipherText = encode('Testing,1 2 3, testing.')
expect(cipherText).toEqual('gvhgr mt123 gvhgr mt')
})
it('encode deep thought', () => {
const cipherText = encode('Truth is fiction.')
expect(cipherText).toEqual('gifgs rhurx grlm')
})
it('encode all the letters', () => {
const cipherText = encode('thequickbrownfoxjumpsoverthelazydog')
expect(cipherText).toEqual('gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt')
})
})
describe('decode', () => {
it('decode exercism', () => {
const plainText = decode('vcvix rhn')
expect(plainText).toEqual('exercism')
})
it('decode a sentence', () => {
const cipherText = decode('zmlyh gzxov rhlug vmzhg vkkrm thglm v')
expect(cipherText).toEqual('anobstacleisoftenasteppingstone')
})
it('decode numbers', () => {
const plainText = decode('gvhgr mt123 gvhgr mt')
expect(plainText).toEqual('testing123testing')
})
it('decode all the letters', () => {
const cipherText = decode('gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt')
expect(cipherText).toEqual('thequickbrownfoxjumpsoverthelazydog')
})
})
})
+21
View File
@@ -0,0 +1,21 @@
const originalAlphabet = 'abcdefghijklmnopqrstuvwxyz'
const cipherAlphabet = 'zyxwvutsrqponmlkjihgfedcba'
export function encode(plainText: string): string {
return plainText
.toLowerCase()
.split('')
.map(c => c.match(/\d/) ? c : cipherAlphabet[originalAlphabet.indexOf(c)])
.filter(Boolean)
.map((c, i) => (i + 1) % 5 === 0 ? `${c} ` : c)
.join("")
.trim()
}
export function decode(cipherText: string): string {
return cipherText
.split('')
.map(c => c.match(/\d/) ? c : originalAlphabet[cipherAlphabet.indexOf(c)])
.filter(Boolean)
.join("")
}
@@ -0,0 +1,5 @@
module.exports = {
// eslint-disable-next-line @typescript-eslint/no-require-imports
presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]],
plugins: [],
}
@@ -0,0 +1,26 @@
// @ts-check
import tsEslint from 'typescript-eslint'
import config from '@exercism/eslint-config-typescript'
import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs'
export default [
...tsEslint.config(...config, {
files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
extends: maintainersConfig,
}),
{
ignores: [
// # Protected or generated
'.git/**/*',
'.vscode/**/*',
//# When using npm
'node_modules/**/*',
// # Configuration files
'babel.config.cjs',
'jest.config.cjs',
],
},
]
+22
View File
@@ -0,0 +1,22 @@
module.exports = {
verbose: true,
projects: ['<rootDir>'],
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/test/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
],
testPathIgnorePatterns: [
'/(?:production_)?node_modules/',
'.d.ts$',
'<rootDir>/test/fixtures',
'<rootDir>/test/helpers',
'__mocks__',
],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
moduleNameMapper: {
'^(\\.\\/.+)\\.js$': '$1',
},
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@exercism/typescript-atbash-cipher",
"version": "1.0.0",
"description": "Exercism exercises in Typescript.",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/typescript"
},
"type": "module",
"engines": {
"node": "^18.16.0 || >=20.0.0"
},
"devDependencies": {
"@exercism/babel-preset-typescript": "^0.6.0",
"@exercism/eslint-config-typescript": "^0.8.0",
"@jest/globals": "^29.7.0",
"@types/node": "~22.7.6",
"babel-jest": "^29.7.0",
"core-js": "~3.38.1",
"eslint": "^9.12.0",
"expect": "^29.7.0",
"jest": "^29.7.0",
"prettier": "^3.5.3",
"tstyche": "^2.1.1",
"typescript": "~5.6.3",
"typescript-eslint": "^8.10.0"
},
"scripts": {
"test": "corepack yarn node test-runner.mjs",
"test:types": "corepack yarn tstyche",
"test:implementation": "corepack yarn jest --no-cache --passWithNoTests",
"lint": "corepack yarn lint:types && corepack yarn lint:ci",
"lint:types": "corepack yarn tsc --noEmit -p .",
"lint:ci": "corepack yarn eslint . --ext .tsx,.ts"
},
"packageManager": "yarn@4.5.1"
}
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env node
/**
* 👋🏽 Hello there reader,
*
* It looks like you are working on this solution using the Exercism CLI and
* not the online editor. That's great! The file you are looking at executes
* the various steps the online test-runner also takes.
*
* @see https://github.com/exercism/typescript-test-runner
*
* TypeScript track exercises generally consist of at least two out of three
* types of tests to run.
*
* 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid
* 2. tstyche, static analysis tests to see if the types used are expected
* 3. jest, runtime implementation tests to see if the solution is correct
*
* If one of these three fails, this script terminates with -1, -2, or -3
* respectively. If it succeeds, it terminates with exit code 0.
*
* @note you need corepack (bundled with node LTS) enabled in order for this
* test runner to work as expected. Follow the installation and test
* instructions if you see errors about corepack or pnp.
*/
import { execSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { exit } from 'node:process'
import { URL } from 'node:url'
/**
* Before executing any tests, the test runner attempts to find the
* exercise config.json file which has metadata about which types of tests
* to run for this solution.
*/
const metaDirectory = new URL('./.meta/', import.meta.url)
const exercismDirectory = new URL('./.exercism/', import.meta.url)
const configDirectory = existsSync(metaDirectory)
? metaDirectory
: existsSync(exercismDirectory)
? exercismDirectory
: null
if (configDirectory === null) {
throw new Error(
'Expected .meta or .exercism directory to exist, but I cannot find it.'
)
}
const configFile = new URL('./config.json', configDirectory)
if (!existsSync(configFile)) {
throw new Error('Expected config.json to exist at ' + configFile.toString())
}
// Experimental: import config from './config.json' with { type: 'json' }
/** @type {import('./config.json') } */
const config = JSON.parse(readFileSync(configFile))
const jest = !config.custom || config.custom['flag.tests.jest']
const tstyche = config.custom?.['flag.tests.tstyche']
console.log(
`[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, `
)
/**
* 1. tsc: the typescript compiler
*/
try {
console.log('[tests] tsc (compile)')
execSync('corepack yarn lint:types', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-1)
}
/**
* 2. tstyche: type tests
*/
if (tstyche) {
try {
console.log('[tests] tstyche (type tests)')
execSync('corepack yarn test:types', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-2)
}
}
/**
* 3. jest: implementation tests
*/
if (jest) {
try {
console.log('[tests] tstyche (implementation tests)')
execSync('corepack yarn test:implementation', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-3)
}
}
/**
* Done! 🥳
*/
+38
View File
@@ -0,0 +1,38 @@
{
"display": "Configuration for Exercism TypeScript Exercises",
"compilerOptions": {
// Allows you to use the newest syntax, and have access to console.log
// https://www.typescriptlang.org/tsconfig#lib
"lib": ["ES2020", "dom"],
// Make sure typescript is configured to output ESM
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
"module": "Node16",
// Since this project is using babel, TypeScript may target something very
// high, and babel will make sure it runs on your local Node version.
// https://babeljs.io/docs/en/
"target": "ES2020", // ESLint doesn't support this yet: "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// Because jest-resolve isn't like node resolve, the absolute path must be .ts
"allowImportingTsExtensions": true,
"noEmit": true,
// Because we'll be using babel: ensure that Babel can safely transpile
// files in the TypeScript project.
//
// https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats
"isolatedModules": true
},
"include": [
"*.ts",
"*.tsx",
".meta/*.ts",
".meta/*.tsx",
"__typetests__/*.tst.ts"
],
"exclude": ["node_modules"]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
{
"authors": [
"CRivasGomez"
],
"contributors": [
"masters3d",
"SleeplessByte"
],
"files": {
"solution": [
"collatz-conjecture.ts"
],
"test": [
"collatz-conjecture.test.ts"
],
"example": [
".meta/proof.ci.ts"
]
},
"blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture",
"custom": {
"version.tests.compatibility": "jest-29",
"flag.tests.task-per-describe": false,
"flag.tests.may-run-long": false,
"flag.tests.includes-optional": false,
"flag.tests.jest": true,
"flag.tests.tstyche": false
},
"source": "An unsolved problem in mathematics named after mathematician Lothar Collatz",
"source_url": "https://en.wikipedia.org/wiki/3x_%2B_1_problem"
}
@@ -0,0 +1 @@
{"track":"typescript","exercise":"collatz-conjecture","id":"6573b4612c77440bb14a040ef214e448","url":"https://exercism.org/tracks/typescript/exercises/collatz-conjecture","handle":"briemens","is_requester":true,"auto_approve":false}
+16691
View File
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"cSpell.words": ["exercism"],
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
}
}
Binary file not shown.
@@ -0,0 +1,3 @@
compressionLevel: mixed
enableGlobalCache: true
+50
View File
@@ -0,0 +1,50 @@
# Help
## Running the tests
Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup.
Execute the tests with:
```bash
$ corepack yarn test
```
## Skipped tests
In the test suites all tests but the first have been skipped.
Once you get a test passing, you can enable the next one by changing `xit` to `it`.
Additionally tests may be grouped using `xdescribe`.
Enable the group by changing that to `describe`.
Finally, some exercises may have optional tests `it.skip`.
Remove `.skip` to execute the optional test.
## Submitting your solution
You can submit your solution using the `exercism submit collatz-conjecture.ts` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript)
- The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html)
- [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf)
- [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference)
- [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit.
- [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
+49
View File
@@ -0,0 +1,49 @@
# Collatz Conjecture
Welcome to Collatz Conjecture on Exercism's TypeScript Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
The Collatz Conjecture or 3x+1 problem can be summarized as follows:
Take any positive integer n.
If n is even, divide n by 2 to get n / 2.
If n is odd, multiply n by 3 and add 1 to get 3n + 1.
Repeat the process indefinitely.
The conjecture states that no matter which number you start with, you will always reach 1 eventually.
Given a number n, return the number of steps required to reach 1.
## Examples
Starting with n = 12, the steps would be as follows:
0. 12
1. 6
2. 3
3. 10
4. 5
5. 16
6. 8
7. 4
8. 2
9. 1
Resulting in 9 steps.
So for input n = 12, the return value would be 9.
## Source
### Created by
- @CRivasGomez
### Contributed to by
- @masters3d
- @SleeplessByte
### Based on
An unsolved problem in mathematics named after mathematician Lothar Collatz - https://en.wikipedia.org/wiki/3x_%2B_1_problem
@@ -0,0 +1,5 @@
module.exports = {
// eslint-disable-next-line @typescript-eslint/no-require-imports
presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]],
plugins: [],
}
@@ -0,0 +1,45 @@
import { describe, it, expect, xit } from '@jest/globals'
import { steps } from './collatz-conjecture.ts'
describe('CollatzConjecture', () => {
it('zero steps for one', () => {
const expected = 0
expect(steps(1)).toBe(expected)
})
it('divide if even', () => {
const expected = 4
expect(steps(16)).toBe(expected)
})
it('even and odd steps', () => {
const expected = 9
expect(steps(12)).toBe(expected)
})
it('Large number of even and odd steps', () => {
const expected = 152
expect(steps(1000000)).toBe(expected)
})
it('zero is an error', () => {
const expected = 'Only positive integers are allowed'
expect(() => {
steps(0)
}).toThrow(expected)
})
it('negative value is an error', () => {
const expected = 'Only positive integers are allowed'
expect(() => {
steps(-15)
}).toThrow(expected)
})
it('non-integer value is an error', () => {
const expected = 'Only positive integers are allowed'
expect(() => {
steps(3.1415)
}).toThrow(expected)
})
})
@@ -0,0 +1,14 @@
function step(count: number, interation: number): number {
if (count === 1) return interation
if (count % 2 === 0) { // Even
return step(count / 2, interation + 1)
} else {
return step((3 * count) + 1, interation + 1)
}
}
export function steps(count: number): number {
if (!count || count < 1 || Math.trunc(count) !== count)
throw new Error('Only positive integers are allowed')
return step(count, 0)
}
@@ -0,0 +1,26 @@
// @ts-check
import tsEslint from 'typescript-eslint'
import config from '@exercism/eslint-config-typescript'
import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs'
export default [
...tsEslint.config(...config, {
files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
extends: maintainersConfig,
}),
{
ignores: [
// # Protected or generated
'.git/**/*',
'.vscode/**/*',
//# When using npm
'node_modules/**/*',
// # Configuration files
'babel.config.cjs',
'jest.config.cjs',
],
},
]
@@ -0,0 +1,22 @@
module.exports = {
verbose: true,
projects: ['<rootDir>'],
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/test/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
],
testPathIgnorePatterns: [
'/(?:production_)?node_modules/',
'.d.ts$',
'<rootDir>/test/fixtures',
'<rootDir>/test/helpers',
'__mocks__',
],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
moduleNameMapper: {
'^(\\.\\/.+)\\.js$': '$1',
},
}
@@ -0,0 +1,38 @@
{
"name": "@exercism/typescript-collatz-conjecture",
"version": "1.0.0",
"description": "Exercism exercises in Typescript.",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/typescript"
},
"type": "module",
"engines": {
"node": "^18.16.0 || >=20.0.0"
},
"devDependencies": {
"@exercism/babel-preset-typescript": "^0.6.0",
"@exercism/eslint-config-typescript": "^0.8.0",
"@jest/globals": "^29.7.0",
"@types/node": "~22.7.6",
"babel-jest": "^29.7.0",
"core-js": "~3.38.1",
"eslint": "^9.12.0",
"expect": "^29.7.0",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"tstyche": "^2.1.1",
"typescript": "~5.6.3",
"typescript-eslint": "^8.10.0"
},
"scripts": {
"test": "corepack yarn node test-runner.mjs",
"test:types": "corepack yarn tstyche",
"test:implementation": "corepack yarn jest --no-cache --passWithNoTests",
"lint": "corepack yarn lint:types && corepack yarn lint:ci",
"lint:types": "corepack yarn tsc --noEmit -p .",
"lint:ci": "corepack yarn eslint . --ext .tsx,.ts"
},
"packageManager": "yarn@4.5.1"
}
@@ -0,0 +1,111 @@
#!/usr/bin/env node
/**
* 👋🏽 Hello there reader,
*
* It looks like you are working on this solution using the Exercism CLI and
* not the online editor. That's great! The file you are looking at executes
* the various steps the online test-runner also takes.
*
* @see https://github.com/exercism/typescript-test-runner
*
* TypeScript track exercises generally consist of at least two out of three
* types of tests to run.
*
* 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid
* 2. tstyche, static analysis tests to see if the types used are expected
* 3. jest, runtime implementation tests to see if the solution is correct
*
* If one of these three fails, this script terminates with -1, -2, or -3
* respectively. If it succeeds, it terminates with exit code 0.
*
* @note you need corepack (bundled with node LTS) enabled in order for this
* test runner to work as expected. Follow the installation and test
* instructions if you see errors about corepack or pnp.
*/
import { execSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { exit } from 'node:process'
import { URL } from 'node:url'
/**
* Before executing any tests, the test runner attempts to find the
* exercise config.json file which has metadata about which types of tests
* to run for this solution.
*/
const metaDirectory = new URL('./.meta/', import.meta.url)
const exercismDirectory = new URL('./.exercism/', import.meta.url)
const configDirectory = existsSync(metaDirectory)
? metaDirectory
: existsSync(exercismDirectory)
? exercismDirectory
: null
if (configDirectory === null) {
throw new Error(
'Expected .meta or .exercism directory to exist, but I cannot find it.'
)
}
const configFile = new URL('./config.json', configDirectory)
if (!existsSync(configFile)) {
throw new Error('Expected config.json to exist at ' + configFile.toString())
}
// Experimental: import config from './config.json' with { type: 'json' }
/** @type {import('./config.json') } */
const config = JSON.parse(readFileSync(configFile))
const jest = !config.custom || config.custom['flag.tests.jest']
const tstyche = config.custom?.['flag.tests.tstyche']
console.log(
`[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, `
)
/**
* 1. tsc: the typescript compiler
*/
try {
console.log('[tests] tsc (compile)')
execSync('corepack yarn lint:types', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-1)
}
/**
* 2. tstyche: type tests
*/
if (tstyche) {
try {
console.log('[tests] tstyche (type tests)')
execSync('corepack yarn test:types', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-2)
}
}
/**
* 3. jest: implementation tests
*/
if (jest) {
try {
console.log('[tests] tstyche (implementation tests)')
execSync('corepack yarn test:implementation', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-3)
}
}
/**
* Done! 🥳
*/
@@ -0,0 +1,38 @@
{
"display": "Configuration for Exercism TypeScript Exercises",
"compilerOptions": {
// Allows you to use the newest syntax, and have access to console.log
// https://www.typescriptlang.org/tsconfig#lib
"lib": ["ES2020", "dom"],
// Make sure typescript is configured to output ESM
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
"module": "Node16",
// Since this project is using babel, TypeScript may target something very
// high, and babel will make sure it runs on your local Node version.
// https://babeljs.io/docs/en/
"target": "ES2020", // ESLint doesn't support this yet: "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// Because jest-resolve isn't like node resolve, the absolute path must be .ts
"allowImportingTsExtensions": true,
"noEmit": true,
// Because we'll be using babel: ensure that Babel can safely transpile
// files in the TypeScript project.
//
// https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats
"isolatedModules": true
},
"include": [
"*.ts",
"*.tsx",
".meta/*.ts",
".meta/*.tsx",
"__typetests__/*.tst.ts"
],
"exclude": ["node_modules"]
}
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
!.meta
# Protected or generated
.git
.vscode
# When using npm
node_modules/*
# Configuration files
.eslintrc.cjs
babel.config.cjs
jest.config.cjs
+38
View File
@@ -0,0 +1,38 @@
module.exports = {
root: true,
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
overrides: [
// Student provided files
{
files: ['*.ts'],
excludedFiles: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
extends: '@exercism/eslint-config-typescript',
},
// Exercism given tests
{
files: ['*.test.ts'],
excludedFiles: ['custom.test.ts'],
env: {
jest: true,
},
extends: '@exercism/eslint-config-typescript/maintainers',
},
// Student provided tests
{
files: ['custom.test.ts'],
env: {
jest: true,
},
extends: '@exercism/eslint-config-typescript',
},
// Exercism provided files
{
files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
excludedFiles: ['custom.test.ts'],
extends: '@exercism/eslint-config-typescript/maintainers',
},
],
}
+23
View File
@@ -0,0 +1,23 @@
{
"authors": [
"CRivasGomez"
],
"contributors": [
"archanid",
"masters3d",
"paparomeo",
"SleeplessByte"
],
"files": {
"solution": [
"list-ops.ts"
],
"test": [
"list-ops.test.ts"
],
"example": [
".meta/proof.ci.ts"
]
},
"blurb": "Implement basic list operations."
}
@@ -0,0 +1 @@
{"track":"typescript","exercise":"list-ops","id":"28dcbca94bdd4bd18de25e999a78ad6b","url":"https://exercism.org/tracks/typescript/exercises/list-ops","handle":"briemens","is_requester":true,"auto_approve":false}
Generated Executable
+19599
View File
File diff suppressed because one or more lines are too long
+2047
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
yarnPath: .yarn/releases/yarn-3.6.0.cjs
+44
View File
@@ -0,0 +1,44 @@
# Help
## Running the tests
Execute the tests with:
```bash
$ yarn test
```
## Skipped tests
In the test suites all tests but the first have been skipped.
Once you get a test passing, you can enable the next one by changing `xit` to
`it`.
## Submitting your solution
You can submit your solution using the `exercism submit list-ops.ts` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html)
- [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf)
- [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference)
- [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit.
- [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
+47
View File
@@ -0,0 +1,47 @@
# List Ops
Welcome to List Ops on Exercism's TypeScript Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Implement basic list operations.
In functional languages list operations like `length`, `map`, and `reduce` are very common.
Implement a series of basic list operations, without using existing functions.
The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include:
- `append` (_given two lists, add all items in the second list to the end of the first list_);
- `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_);
- `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_);
- `length` (_given a list, return the total number of items within it_);
- `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_);
- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using `function(accumulator, item)`_);
- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`_);
- `reverse` (_given a list, return a list with all the original items, but in reversed order_);
Using core language features to build and deconstruct arrays via destructuring, and using the array literal `[]` are allowed, but no functions from the `Array.prototype` should be used.
In order to be able to test your solution, ensure `forEach` is implemented.
```typescript
const list = List.create(1, 2)
list.forEach((item) => console.log(item))
// =>
// 1
// 2
```
## Source
### Created by
- @CRivasGomez
### Contributed to by
- @archanid
- @masters3d
- @paparomeo
- @SleeplessByte
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
presets: ['@exercism/babel-preset-typescript'],
plugins: [],
}
+19
View File
@@ -0,0 +1,19 @@
module.exports = {
verbose: true,
projects: ['<rootDir>'],
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/test/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
],
testPathIgnorePatterns: [
'/(?:production_)?node_modules/',
'.d.ts$',
'<rootDir>/test/fixtures',
'<rootDir>/test/helpers',
'__mocks__',
],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
}
+170
View File
@@ -0,0 +1,170 @@
import { List } from './list-ops'
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest {
interface Matchers<R> {
toHaveValues(...expected: unknown[]): CustomMatcherResult
}
}
}
expect.extend({
toHaveValues(
received: ReturnType<typeof List.create>,
...expected: unknown[]
): jest.CustomMatcherResult {
if (!('forEach' in received)) {
return {
pass: false,
message: (): string => `Implement .forEach(callback) on your list`,
}
}
const values: unknown[] = []
received.forEach((item) => {
values.push(item)
})
const pass = JSON.stringify(values) === JSON.stringify(expected)
return {
pass,
message: (): string =>
pass
? ''
: `Expected to see the following values: ${JSON.stringify(
expected
)}, actual: ${JSON.stringify(values)}`,
}
},
})
describe('append entries to a list and return the new list', () => {
it('empty lists', () => {
const list1 = List.create()
const list2 = List.create()
expect(list1.append(list2)).toEqual(List.create())
})
it('list to empty list', () => {
const list1 = List.create()
const list2 = List.create(1, 2, 3, 4)
expect(list1.append(list2)).toEqual(list2)
})
it('empty list to list', () => {
const list1 = List.create(1, 2, 3, 4)
const list2 = List.create()
expect(list1.append(list2)).toEqual(list1)
})
it('non-empty lists', () => {
const list1 = List.create(1, 2)
const list2 = List.create(2, 3, 4, 5)
expect(list1.append(list2)).toHaveValues(1, 2, 2, 3, 4, 5)
})
})
describe('concat lists and lists of lists into new list', () => {
it('empty list', () => {
const list1 = List.create()
const list2 = List.create()
expect(list1.concat(list2)).toHaveValues()
})
it('list of lists', () => {
const list1 = List.create(1, 2)
const list2 = List.create(3)
const list3 = List.create()
const list4 = List.create(4, 5, 6)
const listOfLists = List.create(list2, list3, list4)
expect(list1.concat(listOfLists)).toHaveValues(1, 2, 3, 4, 5, 6)
})
})
describe('filter list returning only values that satisfy the filter function', () => {
it('empty list', () => {
const list1 = List.create()
expect(list1.filter<number>((el) => el % 2 === 1)).toHaveValues()
})
it('non empty list', () => {
const list1 = List.create(1, 2, 3, 5)
expect(list1.filter<number>((el) => el % 2 === 1)).toHaveValues(1, 3, 5)
})
})
describe('returns the length of a list', () => {
it('empty list', () => {
const list1 = List.create()
expect(list1.length()).toEqual(0)
})
it('non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.length()).toEqual(4)
})
})
describe('returns a list of elements whose values equal the list value transformed by the mapping function', () => {
it('empty list', () => {
const list1 = List.create()
expect(list1.map<number>((el) => ++el)).toHaveValues()
})
it('non-empty list', () => {
const list1 = List.create(1, 3, 5, 7)
expect(list1.map<number>((el) => ++el)).toHaveValues(2, 4, 6, 8)
})
})
describe('folds (reduces) the given list from the left with a function', () => {
xit('empty list', () => {
const list1 = List.create()
expect(list1.foldl<number, number>((acc, el) => el * acc, 2)).toEqual(2)
})
xit('direction independent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldl<number, number>((acc, el) => acc + el, 5)).toEqual(15)
})
xit('direction dependent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldl<number, number>((acc, el) => el / acc, 24)).toEqual(64)
})
})
describe('folds (reduces) the given list from the right with a function', () => {
xit('empty list', () => {
const list1 = List.create()
expect(list1.foldr<number, number>((acc, el) => el * acc, 2)).toEqual(2)
})
xit('direction independent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldr<number, number>((acc, el) => acc + el, 5)).toEqual(15)
})
xit('direction dependent function applied to non-empty list', () => {
const list1 = List.create(1, 2, 3, 4)
expect(list1.foldr<number, number>((acc, el) => el / acc, 24)).toEqual(9)
})
})
describe('reverse the elements of a list', () => {
xit('empty list', () => {
const list1 = List.create()
expect(list1.reverse()).toHaveValues()
})
xit('non-empty list', () => {
const list1 = List.create(1, 3, 5, 7)
expect(list1.reverse()).toHaveValues(7, 5, 3, 1)
})
xit('list of lists is not flattened', () => {
const list1 = List.create([1, 2], [3], [], [4, 5, 6])
expect(list1.reverse()).toHaveValues([4, 5, 6], [], [3], [1, 2])
})
})
+53
View File
@@ -0,0 +1,53 @@
type ListValue = number
type ListValues = ListValue[]
type ConsturctorValue = ListValue | List
type ConsturctorValues = ConsturctorValue[]
export class List {
public values: ListValue[]
public constructor(...values: ListValues) { this.values = values }
public static create = (...values: ConsturctorValues): List => {
if (values[0] instanceof List) {
return values[0].append(List.create(...values.slice(1)))
} else if (typeof values[0] === 'number') {
return new List(values[0]).append(List.create(...values.slice(1)))
} else {
return new List()
}
}
length(): number {
let count = 0
for (let _ of this.values) count++
return count
}
append(list: List): List { return new List(...[...this.values, ...list.values]); }
forEach(callback: (value: unknown) => void): void {
for (const value of this.values) {
callback(value)
}
}
concat(...lists: List[]): List {
if (!lists.length) return this
return this.append(lists[0]).concat(...lists.slice(1))
}
filter<T extends ListValue>(predicate: (value: ListValue) => boolean): List {
if (!this.length()) return this
if (predicate(this.values[0])) {
return List.create(this.values[0]).concat(List.create(...this.values.slice(1)).filter<T>(predicate));
}
return List.create(...this.values.slice(1)).filter<T>(predicate)
}
map<T extends ListValue>(predicate: (value: ListValue) => ListValue): List {
if (!this.length()) return this
return List.create(predicate(this.values[0])).concat(List.create(...this.values.slice(1)).map<T>(predicate));
}
}
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@exercism/typescript-list-ops",
"version": "1.0.0",
"description": "Exercism exercises in Typescript.",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/typescript"
},
"type": "module",
"engines": {
"node": "^18.16.0 || >=20.0.0"
},
"devDependencies": {
"@exercism/babel-preset-typescript": "^0.4.0",
"@exercism/eslint-config-typescript": "^0.5.0",
"@types/jest": "^29.5.3",
"@types/node": "~18.16.16",
"babel-jest": "^29.5.0",
"core-js": "~3.30.2",
"eslint": "^8.42.0",
"jest": "^29.5.0",
"typescript": "~5.0.4"
},
"scripts": {
"watch": "jest --no-cache --watch",
"test": "yarn lint:types && jest --no-cache",
"lint": "yarn lint:types && yarn lint:ci",
"lint:types": "yarn tsc --noEmit -p .",
"lint:ci": "eslint . --ext .tsx,.ts"
},
"packageManager": "yarn@3.6.0",
"dependencies": {
"@babel/core": "^7.22.9",
"@types/mocha": "^10.0.1"
}
}
+28
View File
@@ -0,0 +1,28 @@
{
"display": "Configuration for Exercism TypeScript Exercises",
"compilerOptions": {
// Allows you to use the newest syntax, and have access to console.log
// https://www.typescriptlang.org/tsconfig#lib
"lib": ["ESNEXT", "dom"],
// Make sure typescript is configured to output ESM
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
"module": "ES2020",
// Since this project is using babel, TypeScript may target something very
// high, and babel will make sure it runs on your local Node version.
// https://babeljs.io/docs/en/
"target": "ESNext", // ESLint doesn't support this yet: "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// Because we'll be using babel: ensure that Babel can safely transpile
// files in the TypeScript project.
//
// https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats
"isolatedModules": true
},
"include": ["*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx"],
"exclude": ["node_modules"]
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
{
"authors": [
"CRivasGomez"
],
"contributors": [
"masters3d",
"SleeplessByte"
],
"files": {
"solution": [
"nucleotide-count.ts"
],
"test": [
"nucleotide-count.test.ts"
],
"example": [
".meta/proof.ci.ts"
]
},
"blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.",
"custom": {
"version.tests.compatibility": "jest-29",
"flag.tests.task-per-describe": false,
"flag.tests.may-run-long": false,
"flag.tests.includes-optional": false,
"flag.tests.jest": true,
"flag.tests.tstyche": false
},
"source": "The Calculating DNA Nucleotides_problem at Rosalind",
"source_url": "https://rosalind.info/problems/dna/"
}
@@ -0,0 +1 @@
{"track":"typescript","exercise":"nucleotide-count","id":"9369cef256f243b184020257cc055c5f","url":"https://exercism.org/tracks/typescript/exercises/nucleotide-count","handle":"briemens","is_requester":true,"auto_approve":false}
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"arcanis.vscode-zipfs",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
+7
View File
@@ -0,0 +1,7 @@
{
"cSpell.words": ["exercism"],
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
}
}
+3
View File
@@ -0,0 +1,3 @@
compressionLevel: mixed
enableGlobalCache: true
+50
View File
@@ -0,0 +1,50 @@
# Help
## Running the tests
Before trying to execute the tests, ensure the assignment folder is set-up correctly by following the installation steps, namely `corepack yarn install` and the Editor SDK setup.
Execute the tests with:
```bash
$ corepack yarn test
```
## Skipped tests
In the test suites all tests but the first have been skipped.
Once you get a test passing, you can enable the next one by changing `xit` to `it`.
Additionally tests may be grouped using `xdescribe`.
Enable the group by changing that to `describe`.
Finally, some exercises may have optional tests `it.skip`.
Remove `.skip` to execute the optional test.
## Submitting your solution
You can submit your solution using the `exercism submit nucleotide-count.ts` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript)
- The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
To get help if you're having trouble, you can use one of the following resources:
- [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html)
- [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf)
- [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference)
- [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit.
- [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.
+43
View File
@@ -0,0 +1,43 @@
# Nucleotide Count
Welcome to Nucleotide Count on Exercism's TypeScript Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
## Instructions
Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed.
All known life depends on DNA!
> Note: You do not need to understand anything about nucleotides or DNA to complete this exercise.
DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine.
A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important!
We call the order of these nucleotides in a bit of DNA a "DNA sequence".
We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides.
'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' for thymine.
Given a string representing a DNA sequence, count how many of each nucleotide is present.
If the string contains characters that aren't A, C, G, or T then it is invalid and you should signal an error.
For example:
```text
"GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2
"INVALID" -> error
```
## Source
### Created by
- @CRivasGomez
### Contributed to by
- @masters3d
- @SleeplessByte
### Based on
The Calculating DNA Nucleotides_problem at Rosalind - https://rosalind.info/problems/dna/
@@ -0,0 +1,5 @@
module.exports = {
// eslint-disable-next-line @typescript-eslint/no-require-imports
presets: [[require('@exercism/babel-preset-typescript'), { corejs: '3.38' }]],
plugins: [],
}
@@ -0,0 +1,26 @@
// @ts-check
import tsEslint from 'typescript-eslint'
import config from '@exercism/eslint-config-typescript'
import maintainersConfig from '@exercism/eslint-config-typescript/maintainers.mjs'
export default [
...tsEslint.config(...config, {
files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
extends: maintainersConfig,
}),
{
ignores: [
// # Protected or generated
'.git/**/*',
'.vscode/**/*',
//# When using npm
'node_modules/**/*',
// # Configuration files
'babel.config.cjs',
'jest.config.cjs',
],
},
]
@@ -0,0 +1,22 @@
module.exports = {
verbose: true,
projects: ['<rootDir>'],
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/test/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[jt]s?(x)',
],
testPathIgnorePatterns: [
'/(?:production_)?node_modules/',
'.d.ts$',
'<rootDir>/test/fixtures',
'<rootDir>/test/helpers',
'__mocks__',
],
transform: {
'^.+\\.[jt]sx?$': 'babel-jest',
},
moduleNameMapper: {
'^(\\.\\/.+)\\.js$': '$1',
},
}
@@ -0,0 +1,55 @@
import { describe, it, expect, xit } from '@jest/globals'
import { nucleotideCounts } from './nucleotide-count.ts'
describe('count all nucleotides in a strand', () => {
it('empty strand', () => {
const expected = {
A: 0,
C: 0,
G: 0,
T: 0,
}
expect(nucleotideCounts('')).toEqual(expected)
})
xit('can count one nucleotide in single-character input', () => {
const expected = {
A: 0,
C: 0,
G: 1,
T: 0,
}
expect(nucleotideCounts('G')).toEqual(expected)
})
xit('strand with repeated nucleotide', () => {
const expected = {
A: 0,
C: 0,
G: 7,
T: 0,
}
expect(nucleotideCounts('GGGGGGG')).toEqual(expected)
})
xit('strand with multiple nucleotides', () => {
const expected = {
A: 20,
C: 12,
G: 17,
T: 21,
}
expect(
nucleotideCounts(
'AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC'
)
).toEqual(expected)
})
xit('strand with invalid nucleotides', () => {
const expected = 'Invalid nucleotide in strand'
expect(() => {
nucleotideCounts('AGXXACT')
}).toThrow(expected)
})
})
@@ -0,0 +1,3 @@
export function nucleotideCounts(/* Parameters go here */) {
throw new Error('Remove this statement and implement this function')
}
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@exercism/typescript-nucleotide-count",
"version": "1.0.0",
"description": "Exercism exercises in Typescript.",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/exercism/typescript"
},
"type": "module",
"engines": {
"node": "^18.16.0 || >=20.0.0"
},
"devDependencies": {
"@exercism/babel-preset-typescript": "^0.6.0",
"@exercism/eslint-config-typescript": "^0.8.0",
"@jest/globals": "^29.7.0",
"@types/node": "~22.7.6",
"babel-jest": "^29.7.0",
"core-js": "~3.38.1",
"eslint": "^9.12.0",
"expect": "^29.7.0",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"tstyche": "^2.1.1",
"typescript": "~5.6.3",
"typescript-eslint": "^8.10.0"
},
"scripts": {
"test": "corepack yarn node test-runner.mjs",
"test:types": "corepack yarn tstyche",
"test:implementation": "corepack yarn jest --no-cache --passWithNoTests",
"lint": "corepack yarn lint:types && corepack yarn lint:ci",
"lint:types": "corepack yarn tsc --noEmit -p .",
"lint:ci": "corepack yarn eslint . --ext .tsx,.ts"
},
"packageManager": "yarn@4.5.1"
}
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env node
/**
* 👋🏽 Hello there reader,
*
* It looks like you are working on this solution using the Exercism CLI and
* not the online editor. That's great! The file you are looking at executes
* the various steps the online test-runner also takes.
*
* @see https://github.com/exercism/typescript-test-runner
*
* TypeScript track exercises generally consist of at least two out of three
* types of tests to run.
*
* 1. tsc, the TypeScript compiler. This tests if the TypeScript code is valid
* 2. tstyche, static analysis tests to see if the types used are expected
* 3. jest, runtime implementation tests to see if the solution is correct
*
* If one of these three fails, this script terminates with -1, -2, or -3
* respectively. If it succeeds, it terminates with exit code 0.
*
* @note you need corepack (bundled with node LTS) enabled in order for this
* test runner to work as expected. Follow the installation and test
* instructions if you see errors about corepack or pnp.
*/
import { execSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { exit } from 'node:process'
import { URL } from 'node:url'
/**
* Before executing any tests, the test runner attempts to find the
* exercise config.json file which has metadata about which types of tests
* to run for this solution.
*/
const metaDirectory = new URL('./.meta/', import.meta.url)
const exercismDirectory = new URL('./.exercism/', import.meta.url)
const configDirectory = existsSync(metaDirectory)
? metaDirectory
: existsSync(exercismDirectory)
? exercismDirectory
: null
if (configDirectory === null) {
throw new Error(
'Expected .meta or .exercism directory to exist, but I cannot find it.'
)
}
const configFile = new URL('./config.json', configDirectory)
if (!existsSync(configFile)) {
throw new Error('Expected config.json to exist at ' + configFile.toString())
}
// Experimental: import config from './config.json' with { type: 'json' }
/** @type {import('./config.json') } */
const config = JSON.parse(readFileSync(configFile))
const jest = !config.custom || config.custom['flag.tests.jest']
const tstyche = config.custom?.['flag.tests.tstyche']
console.log(
`[tests] tsc: ✅, tstyche: ${tstyche ? '✅' : '❌'}, jest: ${jest ? '✅' : '❌'}, `
)
/**
* 1. tsc: the typescript compiler
*/
try {
console.log('[tests] tsc (compile)')
execSync('corepack yarn lint:types', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-1)
}
/**
* 2. tstyche: type tests
*/
if (tstyche) {
try {
console.log('[tests] tstyche (type tests)')
execSync('corepack yarn test:types', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-2)
}
}
/**
* 3. jest: implementation tests
*/
if (jest) {
try {
console.log('[tests] tstyche (implementation tests)')
execSync('corepack yarn test:implementation', {
stdio: 'inherit',
cwd: process.cwd(),
})
} catch {
exit(-3)
}
}
/**
* Done! 🥳
*/
+38
View File
@@ -0,0 +1,38 @@
{
"display": "Configuration for Exercism TypeScript Exercises",
"compilerOptions": {
// Allows you to use the newest syntax, and have access to console.log
// https://www.typescriptlang.org/tsconfig#lib
"lib": ["ES2020", "dom"],
// Make sure typescript is configured to output ESM
// https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
"module": "Node16",
// Since this project is using babel, TypeScript may target something very
// high, and babel will make sure it runs on your local Node version.
// https://babeljs.io/docs/en/
"target": "ES2020", // ESLint doesn't support this yet: "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// Because jest-resolve isn't like node resolve, the absolute path must be .ts
"allowImportingTsExtensions": true,
"noEmit": true,
// Because we'll be using babel: ensure that Babel can safely transpile
// files in the TypeScript project.
//
// https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats
"isolatedModules": true
},
"include": [
"*.ts",
"*.tsx",
".meta/*.ts",
".meta/*.tsx",
"__typetests__/*.tst.ts"
],
"exclude": ["node_modules"]
}