Skip to content

Commit

Permalink
packages/common のテストを jest に移行 (#380)
Browse files Browse the repository at this point in the history
  • Loading branch information
yasuhito authored Aug 8, 2023
1 parent d9860bc commit 8404760
Show file tree
Hide file tree
Showing 19 changed files with 2,716 additions and 967 deletions.
2 changes: 1 addition & 1 deletion packages/common/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
"rules": {
"i18n-text/no-en": "off"
},
"ignorePatterns": ["build.js", "/dist/", "/doc/"]
"ignorePatterns": ["build.js", "/coverage/", "/dist/", "/doc/"]
}
1 change: 1 addition & 0 deletions packages/common/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
coverage/
docs/
7 changes: 7 additions & 0 deletions packages/common/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */

// eslint-disable-next-line import/no-commonjs, no-undef
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
}
10 changes: 5 additions & 5 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"build": "node build.js",
"presize": "npm run build",
"size": "size-limit",
"test": "karma start test/karma.config.cjs && npm run size",
"test": "yarn jest",
"test:ci": "npm test",
"docs": "typedoc --excludeExternals --excludePrivate --excludeProtected"
},
Expand All @@ -29,16 +29,16 @@
"prettier": "@github/prettier-config",
"dependencies": {
"@types/eslint": "~8.21.0",
"eslint": "~8.33",
"fraction.js": "~4.2",
"neverthrow": "^6.0.0",
"pretty-format": "^29.4.2"
},
"devDependencies": {
"@types/qunit": "^2.19.4",
"karma-esbuild": "~2.2",
"karma-qunit": "^4.1.2",
"@types/jest": "^29.5.3",
"jest": "^29.6.2",
"npm-dts": "~1.3",
"qunit": "~2.19"
"ts-jest": "^29.1.1"
},
"eslintIgnore": [
"dist/",
Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/angle-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export function radian(angle: string): number {
return fraction * Math.PI
}

// TODO: neverthrow 化
export function angleDenominator(angle: string): number {
const coefficient = piCoefficient(angle)
const split = coefficient.split('/')
Expand All @@ -20,6 +21,7 @@ export function angleDenominator(angle: string): number {
}
}

// TODO: neverthrow 化
export function angleNumerator(angle: string): number {
const coefficient = piCoefficient(angle)
const split = coefficient.split('/')
Expand Down
156 changes: 60 additions & 96 deletions packages/common/src/complex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
* limitations under the License.
*/

import {DetailedError} from './detailed-error'
import {Format} from './format'
import {Util} from './util'
import {ok, err, Result} from 'neverthrow'

export class Complex {
static readonly ZERO = new Complex(0, 0)
Expand All @@ -27,98 +26,83 @@ export class Complex {
public imag: number

static from(v: number | Complex): Complex {
if (v instanceof Complex) {
return v
}
if (typeof v === 'number') {
return new Complex(v, 0)
}
throw new DetailedError('Unrecognized value type.', {v})
}

static polar(magnitude: number, phase: number): Complex {
const [cos, sin] = Util.snappedCosSin(phase)
return new Complex(magnitude * cos, magnitude * sin)
return v
}

static realPartOf(v: number | Complex): number {
if (v instanceof Complex) {
return v.real
}
if (typeof v === 'number') {
return v
}
throw new DetailedError('Unrecognized value type.', {v})

return v.real
}

static imagPartOf(v: number | Complex): number {
if (v instanceof Complex) {
return v.imag
}
if (typeof v === 'number') {
return 0
}
throw new DetailedError('Unrecognized value type.', {v})
}

constructor(real: number, imag: number) {
this.real = real
this.imag = imag
return v.imag
}

static rootsOfQuadratic(a: number | Complex, b: number | Complex, c: number | Complex): Complex[] {
a = Complex.from(a)
b = Complex.from(b)
c = Complex.from(c)
static polar(magnitude: number, phase: number): Complex {
const [cos, sin] = this.snappedCosSin(phase)
return new Complex(magnitude * cos, magnitude * sin)
}

if (a.isEqualTo(0)) {
if (!b.isEqualTo(0)) {
return [c.times(-1).dividedBy(b)]
}
if (!c.isEqualTo(0)) {
return []
}
throw Error('Degenerate')
private static snappedCosSin(radians: number): number[] {
const unit = Math.PI / 4
const i = Math.round(radians / unit)
if (i * unit === radians) {
const s = Math.sqrt(0.5)
const snaps = [
[1, 0],
[s, s],
[0, 1],
[-s, s],
[-1, 0],
[-s, -s],
[0, -1],
[s, -s],
]
return snaps[i & 7]
}
return [Math.cos(radians), Math.sin(radians)]
}

const difs = b.times(b).minus(a.times(c).times(4)).sqrts()
const mid = b.times(-1)
const denom = a.times(2)
return difs.map(d => mid.minus(d).dividedBy(denom))
constructor(real: number, imag: number) {
this.real = real
this.imag = imag
}

isEqualTo(other: unknown): boolean {
if (other instanceof Complex) {
return this.real === other.real && this.imag === other.imag
}
if (typeof other === 'number') {
return this.real === other && this.imag === 0
}
if (other instanceof Complex) {
return this.real === other.real && this.imag === other.imag
}
return false
}

isApproximatelyEqualTo(other: number | Complex | unknown, epsilon: number): boolean {
if (other instanceof Complex || typeof other === 'number') {
if (typeof other === 'number' || other instanceof Complex) {
const d = this.minus(Complex.from(other))
return Math.abs(d.real) <= epsilon && Math.abs(d.imag) <= epsilon && d.abs() <= epsilon
}
return false
}

norm2(): number {
return this.real * this.real + this.imag * this.imag
}

abs(): number {
return Math.sqrt(this.norm2())
conjugate(): Complex {
return new Complex(this.real, -this.imag)
}

unit(): Complex {
const m = this.norm2()
if (m < 0.00001) {
return Complex.polar(1, this.phase())
}
return this.dividedBy(Math.sqrt(m))
neg(): Complex {
return new Complex(-this.real, -this.imag)
}

plus(v: number | Complex): Complex {
Expand All @@ -136,43 +120,36 @@ export class Complex {
return new Complex(this.real * c.real - this.imag * c.imag, this.real * c.imag + this.imag * c.real)
}

dividedBy(v: number | Complex): Complex {
dividedBy(v: number | Complex): Result<Complex, Error> {
const c = Complex.from(v)
const d = c.norm2()
if (d === 0) {
throw new Error('Division by Zero')
return err(Error('Division by Zero'))
}

const n = this.times(c.conjugate())
return new Complex(n.real / d, n.imag / d)
return ok(new Complex(n.real / d, n.imag / d))
}

sqrts(): [Complex] | [Complex, Complex] {
const [r, i] = [this.real, this.imag]
const m = Math.sqrt(Math.sqrt(r * r + i * i))
if (m === 0) {
return [Complex.ZERO]
}
if (i === 0 && r < 0) {
return [new Complex(0, m), new Complex(0, -m)]
}

const a = this.phase() / 2
const c = Complex.polar(m, a)
return [c, c.times(-1)]
norm2(): number {
return this.real * this.real + this.imag * this.imag
}

conjugate(): Complex {
return new Complex(this.real, -this.imag)
abs(): number {
return Math.sqrt(this.norm2())
}

toString(format?: Format): string {
format = format || Format.EXACT
return format.allowAbbreviation ? this.toStringAllowSingleValue(format) : this.toStringBothValues(format)
unit(): Complex {
const m = this.norm2()
if (m < 0.00001) {
return Complex.polar(1, this.phase())
}

return this.dividedBy(Math.sqrt(m))._unsafeUnwrap()
}

neg(): Complex {
return new Complex(-this.real, -this.imag)
phase(): number {
return Math.atan2(this.imag, this.real)
}

raisedTo(exponent: number | Complex): Complex {
Expand All @@ -188,32 +165,19 @@ export class Complex {
return this.ln().times(Complex.from(exponent)).exp()
}

exp(): Complex {
return Complex.polar(Math.exp(this.real), this.imag)
}

cos(): Complex {
const z = this.times(Complex.I)
return z.exp().plus(z.neg().exp()).times(0.5)
}

sin(): Complex {
const z = this.times(Complex.I)
return z.exp().minus(z.neg().exp()).dividedBy(new Complex(0, 2))
toString(format?: Format): string {
format = format || Format.EXACT
return format.allowAbbreviation ? this.toStringAllowSingleValue(format) : this.toStringBothValues(format)
}

tan(): Complex {
return this.sin().dividedBy(this.cos())
private exp(): Complex {
return Complex.polar(Math.exp(this.real), this.imag)
}

ln(): Complex {
private ln(): Complex {
return new Complex(Math.log(this.abs()), this.phase())
}

phase(): number {
return Math.atan2(this.imag, this.real)
}

private toStringAllowSingleValue(format: Format): string {
if (Math.abs(this.imag) <= format.maxAbbreviationError) {
return format.formatFloat(this.real)
Expand Down
20 changes: 0 additions & 20 deletions packages/common/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,6 @@ export class Util {
Util.need(v !== null && v !== undefined, 'notNull')
}

static snappedCosSin(radians: number): number[] {
const unit = Math.PI / 4
const i = Math.round(radians / unit)
if (i * unit === radians) {
const s = Math.sqrt(0.5)
const snaps = [
[1, 0],
[s, s],
[0, 1],
[-s, s],
[-1, 0],
[-s, -s],
[0, -1],
[s, -s],
]
return snaps[i & 7]
}
return [Math.cos(radians), Math.sin(radians)]
}

// 現在の URL をパースし、最後の / 以降をデコードしたものを返す
static get urlJson(): string {
const url = new URL(location.href, window.location.origin)
Expand Down
Loading

0 comments on commit 8404760

Please sign in to comment.