Multiple exercises were added to the TypeScript track in this commit.
This commit is contained in:
@@ -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
File diff suppressed because one or more lines are too long
Generated
+2126
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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',
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -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-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"
|
||||
}
|
||||
@@ -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
@@ -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
File diff suppressed because one or more lines are too long
+2126
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -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}
|
||||
+19599
File diff suppressed because one or more lines are too long
Generated
+2047
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+874
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
yarnPath: .yarn/releases/yarn-3.6.0.cjs
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
presets: ['@exercism/babel-preset-typescript'],
|
||||
plugins: [],
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
}
|
||||
@@ -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])
|
||||
})
|
||||
})
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"arcanis.vscode-zipfs",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"cSpell.words": ["exercism"],
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: true
|
||||
@@ -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.
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
Reference in New Issue
Block a user