sportident.js 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.dependency-cruiser.js +233 -0
  2. package/.editorconfig +12 -0
  3. package/.eslintignore +6 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  6. package/.github/workflows/npm.yml +17 -0
  7. package/LICENSE +21 -0
  8. package/README.md +31 -0
  9. package/babel.config.js +21 -0
  10. package/build-docs.sh +25 -0
  11. package/docs/index.md +9 -0
  12. package/docs/typedoc/index.md +11 -0
  13. package/eslint.base.js +232 -0
  14. package/install.sh +6 -0
  15. package/jest.config.ts +49 -0
  16. package/nx.json +39 -0
  17. package/package.json +51 -0
  18. package/src/SiCard/BaseSiCard.test.ts +187 -0
  19. package/src/SiCard/BaseSiCard.ts +101 -0
  20. package/src/SiCard/IRaceResultData.ts +16 -0
  21. package/src/SiCard/ISiCard.ts +23 -0
  22. package/src/SiCard/ISiCardExamples.ts +4 -0
  23. package/src/SiCard/index.ts +2 -0
  24. package/src/SiCard/raceResultTools.test.ts +260 -0
  25. package/src/SiCard/raceResultTools.ts +150 -0
  26. package/src/SiCard/types/FCard.test.ts +19 -0
  27. package/src/SiCard/types/FCard.ts +14 -0
  28. package/src/SiCard/types/ModernSiCard.test.ts +186 -0
  29. package/src/SiCard/types/ModernSiCard.ts +241 -0
  30. package/src/SiCard/types/PCard.test.ts +19 -0
  31. package/src/SiCard/types/PCard.ts +14 -0
  32. package/src/SiCard/types/SIAC.test.ts +84 -0
  33. package/src/SiCard/types/SIAC.ts +19 -0
  34. package/src/SiCard/types/SiCard10.test.ts +85 -0
  35. package/src/SiCard/types/SiCard10.ts +17 -0
  36. package/src/SiCard/types/SiCard11.test.ts +84 -0
  37. package/src/SiCard/types/SiCard11.ts +19 -0
  38. package/src/SiCard/types/SiCard5.test.ts +149 -0
  39. package/src/SiCard/types/SiCard5.ts +129 -0
  40. package/src/SiCard/types/SiCard6.test.ts +179 -0
  41. package/src/SiCard/types/SiCard6.ts +222 -0
  42. package/src/SiCard/types/SiCard8.test.ts +137 -0
  43. package/src/SiCard/types/SiCard8.ts +129 -0
  44. package/src/SiCard/types/SiCard9.test.ts +132 -0
  45. package/src/SiCard/types/SiCard9.ts +128 -0
  46. package/src/SiCard/types/TCard.test.ts +19 -0
  47. package/src/SiCard/types/TCard.ts +14 -0
  48. package/src/SiCard/types/index.test.ts +26 -0
  49. package/src/SiCard/types/index.ts +15 -0
  50. package/src/SiCard/types/modernSiCardExamples.ts +364 -0
  51. package/src/SiCard/types/siCard5Examples.ts +73 -0
  52. package/src/SiCard/types/siCard6Examples.ts +262 -0
  53. package/src/SiCard/types/siCard8Examples.ts +152 -0
  54. package/src/SiCard/types/siCard9Examples.ts +143 -0
  55. package/src/SiDevice/INavigatorWebSerial.ts +78 -0
  56. package/src/SiDevice/INavigatorWebUsb.ts +62 -0
  57. package/src/SiDevice/ISiDevice.ts +48 -0
  58. package/src/SiDevice/ISiDeviceDriver.ts +35 -0
  59. package/src/SiDevice/README.md +13 -0
  60. package/src/SiDevice/SiDevice.test.ts +354 -0
  61. package/src/SiDevice/SiDevice.ts +132 -0
  62. package/src/SiDevice/WebSerialSiDeviceDriver.ts +146 -0
  63. package/src/SiDevice/WebUsbSiDeviceDriver.ts +343 -0
  64. package/src/SiDevice/index.ts +3 -0
  65. package/src/SiDevice/testUtils/index.ts +2 -0
  66. package/src/SiDevice/testUtils/testISiDeviceDriver.ts +63 -0
  67. package/src/SiDevice/testUtils/testISiDeviceDriverWithAutodetection.ts +72 -0
  68. package/src/SiStation/BaseSiStation.test.ts +221 -0
  69. package/src/SiStation/BaseSiStation.ts +253 -0
  70. package/src/SiStation/CoupledSiStation.test.ts +41 -0
  71. package/src/SiStation/CoupledSiStation.ts +130 -0
  72. package/src/SiStation/ISiMainStation.ts +29 -0
  73. package/src/SiStation/ISiSendTask.ts +9 -0
  74. package/src/SiStation/ISiStation.ts +88 -0
  75. package/src/SiStation/ISiTargetMultiplexer.ts +51 -0
  76. package/src/SiStation/SiMainStation.test.ts +222 -0
  77. package/src/SiStation/SiMainStation.ts +133 -0
  78. package/src/SiStation/SiSendTask.ts +50 -0
  79. package/src/SiStation/SiTargetMultiplexer.targeting.test.ts +112 -0
  80. package/src/SiStation/SiTargetMultiplexer.test.ts +605 -0
  81. package/src/SiStation/SiTargetMultiplexer.ts +241 -0
  82. package/src/SiStation/index.ts +5 -0
  83. package/src/SiStation/siStationExamples.ts +103 -0
  84. package/src/constants.test.ts +17 -0
  85. package/src/constants.ts +92 -0
  86. package/src/fakes/FakeSiCard/BaseFakeSiCard.test.ts +11 -0
  87. package/src/fakes/FakeSiCard/BaseFakeSiCard.ts +10 -0
  88. package/src/fakes/FakeSiCard/IFakeSiCard.ts +6 -0
  89. package/src/fakes/FakeSiCard/index.ts +2 -0
  90. package/src/fakes/FakeSiCard/types/FakeModernSiCard.test.ts +62 -0
  91. package/src/fakes/FakeSiCard/types/FakeModernSiCard.ts +43 -0
  92. package/src/fakes/FakeSiCard/types/FakeSIAC.ts +17 -0
  93. package/src/fakes/FakeSiCard/types/FakeSiCard10.ts +17 -0
  94. package/src/fakes/FakeSiCard/types/FakeSiCard11.ts +17 -0
  95. package/src/fakes/FakeSiCard/types/FakeSiCard5.test.ts +42 -0
  96. package/src/fakes/FakeSiCard/types/FakeSiCard5.ts +40 -0
  97. package/src/fakes/FakeSiCard/types/FakeSiCard6.test.ts +62 -0
  98. package/src/fakes/FakeSiCard/types/FakeSiCard6.ts +44 -0
  99. package/src/fakes/FakeSiCard/types/FakeSiCard8.ts +16 -0
  100. package/src/fakes/FakeSiCard/types/FakeSiCard9.ts +16 -0
  101. package/src/fakes/FakeSiCard/types/index.ts +7 -0
  102. package/src/fakes/FakeSiDeviceDriver.ts +148 -0
  103. package/src/fakes/FakeSiMainStation.test.ts +141 -0
  104. package/src/fakes/FakeSiMainStation.ts +118 -0
  105. package/src/fakes/IFakeSiMainStation.ts +15 -0
  106. package/src/fakes/index.ts +2 -0
  107. package/src/index.ts +24 -0
  108. package/src/siProtocol.test.ts +509 -0
  109. package/src/siProtocol.ts +417 -0
  110. package/src/storage/SiArray.test.ts +103 -0
  111. package/src/storage/SiArray.ts +56 -0
  112. package/src/storage/SiBool.test.ts +81 -0
  113. package/src/storage/SiBool.ts +47 -0
  114. package/src/storage/SiDataType.test.ts +81 -0
  115. package/src/storage/SiDataType.ts +60 -0
  116. package/src/storage/SiDict.test.ts +115 -0
  117. package/src/storage/SiDict.ts +60 -0
  118. package/src/storage/SiEnum.test.ts +77 -0
  119. package/src/storage/SiEnum.ts +48 -0
  120. package/src/storage/SiFieldValue.test.ts +58 -0
  121. package/src/storage/SiFieldValue.ts +23 -0
  122. package/src/storage/SiInt.test.ts +80 -0
  123. package/src/storage/SiInt.ts +84 -0
  124. package/src/storage/SiModified.test.ts +135 -0
  125. package/src/storage/SiModified.ts +59 -0
  126. package/src/storage/SiStorage.test.ts +51 -0
  127. package/src/storage/SiStorage.ts +44 -0
  128. package/src/storage/index.test.ts +222 -0
  129. package/src/storage/index.ts +12 -0
  130. package/src/storage/interfaces.ts +41 -0
  131. package/src/storage/siStringEncoding.ts +1361 -0
  132. package/src/testUtils.test.ts +266 -0
  133. package/src/testUtils.ts +75 -0
  134. package/src/utils/NumberRange.test.ts +66 -0
  135. package/src/utils/NumberRange.ts +46 -0
  136. package/src/utils/NumberRangeRegistry.test.ts +49 -0
  137. package/src/utils/NumberRangeRegistry.ts +43 -0
  138. package/src/utils/bytes.test.ts +126 -0
  139. package/src/utils/bytes.ts +69 -0
  140. package/src/utils/errors.test.ts +29 -0
  141. package/src/utils/errors.ts +20 -0
  142. package/src/utils/events.test.ts +112 -0
  143. package/src/utils/events.ts +68 -0
  144. package/src/utils/general.test.ts +139 -0
  145. package/src/utils/general.ts +69 -0
  146. package/src/utils/index.ts +8 -0
  147. package/src/utils/mixins.test.ts +40 -0
  148. package/src/utils/mixins.ts +13 -0
  149. package/src/utils/typed.ts +3 -0
  150. package/tsconfig.base.json +22 -0
  151. package/tsconfig.json +15 -0
@@ -0,0 +1,47 @@
1
+ import _ from 'lodash';
2
+ import { type ISiDataType, type ISiStorageData, ValueFromStringError } from './interfaces';
3
+ import { ModifyUndefinedException, SiDataType } from './SiDataType';
4
+
5
+ export class SiBool extends SiDataType<boolean> implements ISiDataType<boolean> {
6
+ constructor(private byteOffset: number, private bitOffset: number = 0) {
7
+ super();
8
+ }
9
+
10
+ typeSpecificIsValueValid(_value: boolean): boolean {
11
+ return true;
12
+ }
13
+
14
+ typeSpecificValueToString(value: boolean): string {
15
+ return value ? 'true' : 'false';
16
+ }
17
+
18
+ typeSpecificValueFromString(string: string): boolean | ValueFromStringError {
19
+ const valueByString: { [key: string]: boolean } = {
20
+ true: true,
21
+ false: false
22
+ };
23
+ if (valueByString[string] === undefined) {
24
+ return new ValueFromStringError(`Value for ${this.constructor.name} must be "true" or "false", not "${string}"`);
25
+ }
26
+ return valueByString[string];
27
+ }
28
+
29
+ typeSpecificExtractFromData(data: ISiStorageData): boolean | undefined {
30
+ const existingByte = data.get(this.byteOffset);
31
+ if (existingByte === undefined) {
32
+ return undefined;
33
+ }
34
+ return ((existingByte >> this.bitOffset) & 0x01) === 0x01;
35
+ }
36
+
37
+ typeSpecificUpdateData(data: ISiStorageData, newValue: boolean): ISiStorageData {
38
+ const boolAsInt = newValue ? 0x01 : 0x00;
39
+ const preservationMask = (0x01 << this.bitOffset) ^ 0xff;
40
+ const existingByte = data.get(this.byteOffset);
41
+ if (existingByte === undefined) {
42
+ throw new ModifyUndefinedException();
43
+ }
44
+ const newByte = (existingByte & preservationMask) | (boolAsInt << this.bitOffset);
45
+ return data.set(this.byteOffset, newByte);
46
+ }
47
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import Immutable from 'immutable';
4
+ import { type ISiStorageData, ValueFromStringError } from './interfaces';
5
+ import { SiFieldValue } from './SiFieldValue';
6
+ import { SiDataType } from './SiDataType';
7
+
8
+ describe('SiDataType', () => {
9
+ class MyType extends SiDataType<number> {
10
+ typeSpecificIsValueValid(value: number): boolean {
11
+ return _.isInteger(value);
12
+ }
13
+
14
+ typeSpecificValueToString(value: number): string {
15
+ return `->${value}<-`;
16
+ }
17
+
18
+ typeSpecificValueFromString(string: string): number | ValueFromStringError {
19
+ const res = /^->(.*)<-$/.exec(string);
20
+ if (!res) {
21
+ return new ValueFromStringError();
22
+ }
23
+ return parseInt(res[1], 10);
24
+ }
25
+
26
+ typeSpecificExtractFromData(data: ISiStorageData): number | undefined {
27
+ const byte = data.get(0);
28
+ if (byte === undefined) {
29
+ return undefined;
30
+ }
31
+ return byte;
32
+ }
33
+
34
+ typeSpecificUpdateData(data: ISiStorageData, newValue: number): ISiStorageData {
35
+ return data.set(0, newValue);
36
+ }
37
+ }
38
+ const myField = new MyType();
39
+ const fieldValueOf = (intValue: number): SiFieldValue<number> => new SiFieldValue(myField, intValue);
40
+ test('valueToString', () => {
41
+ expect(myField.valueToString(41)).toBe('->41<-');
42
+ });
43
+ test('valueFromString', () => {
44
+ expect(myField.valueFromString('->41<-')).toBe(41);
45
+ expect(myField.valueFromString('41') instanceof ValueFromStringError).toBe(true);
46
+ });
47
+ test('extractFromData', () => {
48
+ const getExtractedFieldValue = (field: SiDataType<number>, bytes: (number | undefined)[]) => field.extractFromData(Immutable.List(bytes));
49
+
50
+ expect(getExtractedFieldValue(myField, [0x61])!.value).toBe(0x61);
51
+ expect(getExtractedFieldValue(myField, [undefined])).toBe(undefined);
52
+ expect(getExtractedFieldValue(myField, [])).toBe(undefined);
53
+ });
54
+ test('updateData', () => {
55
+ const updateData = (field: SiDataType<number>, data: number[], newValue: number | SiFieldValue<number>): number[] => field.updateData(Immutable.List(data), newValue).toJS() as number[];
56
+
57
+ expect(updateData(myField, [0x00], 0x61)).toEqual([0x61]);
58
+ expect(updateData(myField, [0x00], fieldValueOf(0x61))).toEqual([0x61]);
59
+ });
60
+ //
61
+ // test('SiStorage plain SiDataType', () => {
62
+ // class WeirdStorage extends SiStorage {
63
+ // public static size = 0x01;
64
+ // public static definitions = {
65
+ // wtf: new MyType(),
66
+ // };
67
+ // }
68
+ //
69
+ // const myWeirdStorage = new WeirdStorage(
70
+ // utils.unPrettyHex('00'),
71
+ // );
72
+ //
73
+ // expect(() => myWeirdStorage.get('wtf')).toThrow();
74
+ // expect(() => myWeirdStorage.set('wtf', 0xFFFFFFFF)).toThrow();
75
+ // expect(myWeirdStorage.data.toJS()).toEqual(utils.unPrettyHex('00'));
76
+ //
77
+ // expect(myWeirdStorage.get('inexistent')).toBe(undefined);
78
+ // myWeirdStorage.set('inexistent', 0xFFFFFFFF);
79
+ // expect(myWeirdStorage.data.toJS()).toEqual(utils.unPrettyHex('00'));
80
+ // });
81
+ });
@@ -0,0 +1,60 @@
1
+ import _ from 'lodash';
2
+ import { type ISiStorageData, ValueToStringError, ValueFromStringError } from './interfaces';
3
+ import { SiFieldValue } from './SiFieldValue';
4
+
5
+ export class ModifyUndefinedException {
6
+ // eslint-disable-next-line no-useless-constructor
7
+ constructor(
8
+ public message: string = '' // eslint-disable-next-line no-empty-function
9
+ ) {}
10
+ }
11
+
12
+ export abstract class SiDataType<T> {
13
+ isValueValid(value: T): boolean {
14
+ return this.typeSpecificIsValueValid(value);
15
+ }
16
+
17
+ abstract typeSpecificIsValueValid(_value: T): boolean | never;
18
+
19
+ valueToString(value: T): string | ValueToStringError {
20
+ if (!this.isValueValid(value)) {
21
+ return new ValueToStringError(`Value "${value}" is invalid`);
22
+ }
23
+ return this.typeSpecificValueToString(value);
24
+ }
25
+
26
+ abstract typeSpecificValueToString(_value: T): string | ValueToStringError | never;
27
+
28
+ valueFromString(string: string): T | ValueFromStringError {
29
+ const value = this.typeSpecificValueFromString(string);
30
+ if (value instanceof ValueFromStringError) {
31
+ return value;
32
+ }
33
+ if (!this.isValueValid(value)) {
34
+ return new ValueFromStringError(`Value "${value}" is invalid`);
35
+ }
36
+ return value;
37
+ }
38
+
39
+ abstract typeSpecificValueFromString(_string: string): T | ValueFromStringError | never;
40
+
41
+ extractFromData(data: ISiStorageData): SiFieldValue<T> | undefined {
42
+ const extractedValue = this.typeSpecificExtractFromData(data);
43
+ if (extractedValue === undefined) {
44
+ return undefined;
45
+ }
46
+ return new SiFieldValue(this, extractedValue);
47
+ }
48
+
49
+ abstract typeSpecificExtractFromData(_data: ISiStorageData): T | undefined | never;
50
+
51
+ updateData(data: ISiStorageData, newValue: T | SiFieldValue<T>): ISiStorageData {
52
+ const valueForUpdate = newValue instanceof SiFieldValue ? newValue.value : newValue;
53
+ if (!this.typeSpecificIsValueValid(valueForUpdate as T)) {
54
+ throw new TypeError();
55
+ }
56
+ return this.typeSpecificUpdateData(data, valueForUpdate as T);
57
+ }
58
+
59
+ abstract typeSpecificUpdateData(data: ISiStorageData, _newValue: T): ISiStorageData | never;
60
+ }
@@ -0,0 +1,115 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import Immutable from 'immutable';
4
+ import { type ISiStorageData, ValueToStringError, ValueFromStringError } from './interfaces';
5
+ import { ModifyUndefinedException, SiDataType } from './SiDataType';
6
+ import { SiFieldValue } from './SiFieldValue';
7
+ import { SiDict, type SiDictValue } from './SiDict';
8
+
9
+ type FakeSiStorageData = (number | undefined)[];
10
+
11
+ describe('SiDict', () => {
12
+ class FakeDataType extends SiDataType<string> {
13
+ constructor(public index: number) {
14
+ super();
15
+ }
16
+
17
+ typeSpecificIsValueValid(_value: string) {
18
+ return true;
19
+ }
20
+
21
+ typeSpecificValueFromString(str: string): string | ValueFromStringError | never {
22
+ return str.substr(2, str.length - 2);
23
+ }
24
+
25
+ typeSpecificValueToString(value: string) {
26
+ return `->${value}<-`;
27
+ }
28
+
29
+ typeSpecificExtractFromData(data: ISiStorageData): string | undefined {
30
+ const byte = data.get(this.index);
31
+ if (byte === undefined) {
32
+ return undefined;
33
+ }
34
+ return String.fromCharCode(byte);
35
+ }
36
+
37
+ typeSpecificUpdateData(data: ISiStorageData, newValue: string): ISiStorageData {
38
+ const byte = data.get(this.index);
39
+ if (byte === undefined) {
40
+ throw new ModifyUndefinedException();
41
+ }
42
+ return data.set(this.index, newValue.charCodeAt(0));
43
+ }
44
+ }
45
+ const mySiDict = new SiDict({
46
+ One: new FakeDataType(0x00),
47
+ Other: new FakeDataType(0x01)
48
+ });
49
+ type MySiDictType = { One: string; Other: string };
50
+ const fieldValueOf = (value: SiDictValue<MySiDictType>): SiFieldValue<SiDictValue<MySiDictType>> => new SiFieldValue(mySiDict, value);
51
+ test('typeCheckValue', () => {
52
+ expect(mySiDict.isValueValid({ One: 'A', Other: 'B' })).toBe(true);
53
+ expect(mySiDict.isValueValid({ One: 'A', Other: undefined })).toBe(false);
54
+ });
55
+ test('valueToString', () => {
56
+ expect(mySiDict.valueToString({ One: 'A', Other: 'B' })).toBe('One: ->A<-, Other: ->B<-');
57
+ expect(mySiDict.valueToString({ One: 'Y', Other: 'Z' })).toBe('One: ->Y<-, Other: ->Z<-');
58
+ expect(mySiDict.valueToString({ One: 'Y', Other: undefined }) instanceof ValueToStringError).toBe(true);
59
+ });
60
+ test('typeSpecificValueToString handles edge case', () => {
61
+ expect(mySiDict.typeSpecificValueToString({ One: 'Y', Other: undefined })).toBe('One: ->Y<-, Other: ?');
62
+ });
63
+ test('valueFromString', () => {
64
+ expect(mySiDict.valueFromString('One: ->A<-, Other: ->B<-') instanceof ValueFromStringError).toBe(true);
65
+ expect(mySiDict.valueFromString('One: ->Y<-, Other: ->Z<-') instanceof ValueFromStringError).toBe(true);
66
+ expect(mySiDict.valueFromString('test') instanceof ValueFromStringError).toBe(true);
67
+ });
68
+ test('extractFromData gives field value', () => {
69
+ const data = Immutable.List([0x41, 0x42]);
70
+ const fieldValue = mySiDict.extractFromData(data);
71
+ expect(fieldValue instanceof SiFieldValue).toBe(true);
72
+ expect(fieldValue!.field).toBe(mySiDict);
73
+ expect(fieldValue!.value).toEqual({ One: 'A', Other: 'B' });
74
+ });
75
+ test('extractFromData', () => {
76
+ const getExtractedFieldValue = (bytes: (number | undefined)[]) => mySiDict.extractFromData(Immutable.List(bytes));
77
+
78
+ expect(getExtractedFieldValue([0x61, 0x62])!.value).toEqual({ One: 'a', Other: 'b' });
79
+ expect(getExtractedFieldValue([undefined, 0x62])!.value).toEqual({
80
+ One: undefined,
81
+ Other: 'b'
82
+ });
83
+ expect(getExtractedFieldValue([0x61, undefined])!.value).toEqual({
84
+ One: 'a',
85
+ Other: undefined
86
+ });
87
+ expect(getExtractedFieldValue([0x61])!.value).toEqual({ One: 'a', Other: undefined });
88
+ expect(getExtractedFieldValue([])!.value).toEqual({ One: undefined, Other: undefined });
89
+ });
90
+ test('updateData', () => {
91
+ const initialData = Immutable.List([0x00, 0x00]);
92
+ const updateInitialData = (newValue: SiDictValue<MySiDictType> | SiFieldValue<SiDictValue<MySiDictType>>): FakeSiStorageData => mySiDict.updateData(initialData, newValue).toJS();
93
+
94
+ expect(updateInitialData({ One: 'a', Other: 'b' })).toEqual([0x61, 0x62]);
95
+ expect(updateInitialData({ One: 'y', Other: 'z' })).toEqual([0x79, 0x7a]);
96
+ expect(updateInitialData(fieldValueOf({ One: 'a', Other: 'b' }))).toEqual([0x61, 0x62]);
97
+ });
98
+ test('updateData modify undefined', () => {
99
+ const updateData = (data: FakeSiStorageData, newValue: SiDictValue<MySiDictType> | SiFieldValue<SiDictValue<MySiDictType>>): FakeSiStorageData =>
100
+ mySiDict.updateData(Immutable.List(data), newValue).toJS();
101
+
102
+ expect(() => updateData([], { One: 'a', Other: 'b' })).toThrow(ModifyUndefinedException);
103
+ expect(() => updateData([], { One: 'a', Other: 'b' })).toThrow(ModifyUndefinedException);
104
+ expect(() => updateData([], fieldValueOf({ One: 'a', Other: 'b' }))).toThrow(ModifyUndefinedException);
105
+ expect(() => updateData([undefined, 0x00], { One: 'a', Other: 'b' })).toThrow(ModifyUndefinedException);
106
+ expect(() => updateData([0x00, undefined], { One: 'a', Other: 'b' })).toThrow(ModifyUndefinedException);
107
+ expect(() => updateData([0x00], fieldValueOf({ One: 'a', Other: 'b' }))).toThrow(ModifyUndefinedException);
108
+ });
109
+ test('updateData wrong type', () => {
110
+ const initialData = Immutable.List([0x00, 0x00]);
111
+ const updatingInitialData = (newValue: SiDictValue<MySiDictType>) => () => mySiDict.updateData(initialData, newValue);
112
+
113
+ expect(updatingInitialData({ One: undefined, Other: 'A' })).toThrow(TypeError);
114
+ });
115
+ });
@@ -0,0 +1,60 @@
1
+ import { type ISiDataType, type ISiStorageData, ValueFromStringError } from './interfaces';
2
+ import { SiDataType } from './SiDataType';
3
+
4
+ export type SiDictValue<T> = { [key in keyof T]: T[key] | undefined };
5
+ export type SiPartialDictValue<T> = { [key in keyof T]?: T[key] };
6
+
7
+ export class SiDict<T> extends SiDataType<SiDictValue<T>> implements ISiDataType<SiDictValue<T>> {
8
+ constructor(public readonly definitionDict: { [key in keyof T]: ISiDataType<T[key]> }) {
9
+ super();
10
+ }
11
+
12
+ typeSpecificIsValueValid(value: SiDictValue<T>): boolean {
13
+ return this.keysOfT.every((key) => value[key] !== undefined);
14
+ }
15
+
16
+ typeSpecificValueToString(value: SiDictValue<T>): string {
17
+ return this.keysOfT
18
+ .map((key) => {
19
+ const definition = this.definitionDict[key];
20
+ const itemValue = value[key];
21
+ if (itemValue === undefined) {
22
+ return `${String(key)}: ?`;
23
+ }
24
+ // @ts-ignore
25
+ const itemValueString = definition.valueToString(itemValue);
26
+ return `${String(key)}: ${itemValueString}`;
27
+ })
28
+ .join(', ');
29
+ }
30
+
31
+ typeSpecificValueFromString(_string: string): ValueFromStringError {
32
+ return new ValueFromStringError(`${this.constructor.name} does not support string parsing`);
33
+ }
34
+
35
+ typeSpecificExtractFromData(data: ISiStorageData): SiDictValue<T> {
36
+ const dictValue: SiPartialDictValue<T> = {};
37
+ this.keysOfT.forEach((key) => {
38
+ const definition = this.definitionDict[key];
39
+ const itemFieldValue = definition.extractFromData(data);
40
+ if (itemFieldValue === undefined) {
41
+ return;
42
+ }
43
+ dictValue[key] = itemFieldValue.value;
44
+ });
45
+ return dictValue as SiDictValue<T>;
46
+ }
47
+
48
+ typeSpecificUpdateData(data: ISiStorageData, newValue: SiDictValue<T>): ISiStorageData {
49
+ let tempData = data;
50
+ this.keysOfT.forEach((key) => {
51
+ const definition = this.definitionDict[key];
52
+ tempData = definition.updateData(tempData, newValue[key]!);
53
+ });
54
+ return tempData;
55
+ }
56
+
57
+ get keysOfT(): (keyof T)[] {
58
+ return Object.keys(this.definitionDict) as (keyof T)[];
59
+ }
60
+ }
@@ -0,0 +1,77 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import Immutable from 'immutable';
4
+ import { ValueFromStringError } from './interfaces';
5
+ import { SiFieldValue } from './SiFieldValue';
6
+ import { ModifyUndefinedException } from './SiDataType';
7
+ import { SiEnum } from './SiEnum';
8
+
9
+ type FakeSiStorageData = (number | undefined)[];
10
+
11
+ describe('SiEnum', () => {
12
+ const options = { Zero: 0x0, One: 0x1, Two: 0x2, Three: 0x3 };
13
+ const mySiEnum = new SiEnum([[0x00, 4, 6]], options);
14
+ type MySiEnumType = keyof typeof options;
15
+ const fieldValueOf = (value: MySiEnumType): SiFieldValue<MySiEnumType> => new SiFieldValue(mySiEnum, value);
16
+ test('typeCheckValue', () => {
17
+ expect(mySiEnum.isValueValid('Zero')).toBe(true);
18
+ expect(mySiEnum.isValueValid('One')).toBe(true);
19
+ expect(mySiEnum.isValueValid('Two')).toBe(true);
20
+ expect(mySiEnum.isValueValid('Three')).toBe(true);
21
+ });
22
+ test('valueToString', () => {
23
+ expect(mySiEnum.valueToString('Zero')).toBe('Zero');
24
+ expect(mySiEnum.valueToString('One')).toBe('One');
25
+ expect(mySiEnum.valueToString('Two')).toBe('Two');
26
+ expect(mySiEnum.valueToString('Three')).toBe('Three');
27
+ });
28
+ test('valueFromString', () => {
29
+ expect(mySiEnum.valueFromString('Zero')).toBe('Zero');
30
+ expect(mySiEnum.valueFromString('One')).toBe('One');
31
+ expect(mySiEnum.valueFromString('Two')).toBe('Two');
32
+ expect(mySiEnum.valueFromString('Three')).toBe('Three');
33
+ expect(mySiEnum.valueFromString('0') instanceof ValueFromStringError).toBe(true);
34
+ expect(mySiEnum.valueFromString('1') instanceof ValueFromStringError).toBe(true);
35
+ expect(mySiEnum.valueFromString('255') instanceof ValueFromStringError).toBe(true);
36
+ expect(mySiEnum.valueFromString('-1') instanceof ValueFromStringError).toBe(true);
37
+ expect(mySiEnum.valueFromString('test') instanceof ValueFromStringError).toBe(true);
38
+ });
39
+ test('extractFromData gives field value', () => {
40
+ const data = Immutable.List([0x00]);
41
+ const fieldValue = mySiEnum.extractFromData(data);
42
+ expect(fieldValue instanceof SiFieldValue).toBe(true);
43
+ expect(fieldValue!.field).toBe(mySiEnum);
44
+ expect(fieldValue!.value).toBe('Zero');
45
+ });
46
+ test('extractFromData', () => {
47
+ const getExtractedFieldValue = (bytes: FakeSiStorageData) => mySiEnum.extractFromData(Immutable.List(bytes));
48
+
49
+ expect(getExtractedFieldValue([0x00])!.value).toBe('Zero');
50
+ expect(getExtractedFieldValue([0x10])!.value).toBe('One');
51
+ expect(getExtractedFieldValue([0x20])!.value).toBe('Two');
52
+ expect(getExtractedFieldValue([0x30])!.value).toBe('Three');
53
+ expect(getExtractedFieldValue([undefined])).toBe(undefined);
54
+ expect(getExtractedFieldValue([])).toBe(undefined);
55
+ });
56
+ test('updateData', () => {
57
+ const updateData = (data: FakeSiStorageData, newValue: MySiEnumType | SiFieldValue<MySiEnumType>): FakeSiStorageData => mySiEnum.updateData(Immutable.List(data), newValue).toJS();
58
+
59
+ expect(updateData([0xff], 'Zero')).toEqual([0xcf]);
60
+ expect(updateData([0xff], 'One')).toEqual([0xdf]);
61
+ expect(updateData([0xff], 'Two')).toEqual([0xef]);
62
+ expect(updateData([0xff], 'Three')).toEqual([0xff]);
63
+ expect(updateData([0x00], 'Zero')).toEqual([0x00]);
64
+ expect(updateData([0x00], 'One')).toEqual([0x10]);
65
+ expect(updateData([0x00], 'Two')).toEqual([0x20]);
66
+ expect(updateData([0x00], 'Three')).toEqual([0x30]);
67
+ expect(updateData([0xfe], fieldValueOf('Zero'))).toEqual([0xce]);
68
+ });
69
+ test('updateData modify undefined', () => {
70
+ const updateData = (data: FakeSiStorageData, newValue: MySiEnumType | SiFieldValue<MySiEnumType>): FakeSiStorageData => mySiEnum.updateData(Immutable.List(data), newValue).toJS();
71
+
72
+ expect(() => updateData([], 'Zero')).toThrow(ModifyUndefinedException);
73
+ expect(() => updateData([], 'Three')).toThrow(ModifyUndefinedException);
74
+ expect(() => updateData([], fieldValueOf('Two'))).toThrow(ModifyUndefinedException);
75
+ expect(() => updateData([undefined], 'One')).toThrow(ModifyUndefinedException);
76
+ });
77
+ });
@@ -0,0 +1,48 @@
1
+ import _ from 'lodash';
2
+ import * as utils from '../utils';
3
+ import type { ISiDataType, ISiStorageData } from './interfaces';
4
+ import { ValueFromStringError } from './interfaces';
5
+
6
+ import { SiDataType } from './SiDataType';
7
+ import { SiInt, type SiIntegerPartDefinition } from './SiInt';
8
+
9
+ export class SiEnum<T extends { [key: string]: number }> extends SiDataType<keyof T> implements ISiDataType<keyof T> {
10
+ private intField: SiInt;
11
+
12
+ constructor(parts: SiIntegerPartDefinition[], public readonly dict: T, public readonly getIntValue = (value: unknown): number | undefined => value as number) {
13
+ super();
14
+ this.intField = new SiInt(parts);
15
+ }
16
+
17
+ getLookupDict(): { [key: string]: string } {
18
+ return utils.getLookup(this.dict, (value) => `${this.getIntValue(value)}`);
19
+ }
20
+
21
+ typeSpecificIsValueValid(value: keyof T): boolean {
22
+ return this.dict[value] !== undefined;
23
+ }
24
+
25
+ typeSpecificValueToString(value: keyof T): string {
26
+ return String(value);
27
+ }
28
+
29
+ typeSpecificValueFromString(string: string): keyof T | ValueFromStringError {
30
+ if (this.dict[string] === undefined) {
31
+ return new ValueFromStringError(`Value for ${this.constructor.name} must be an enum option, not "${string}"`);
32
+ }
33
+ return string as keyof T;
34
+ }
35
+
36
+ typeSpecificExtractFromData(data: ISiStorageData): keyof T | undefined {
37
+ const intValue = this.intField.typeSpecificExtractFromData(data);
38
+ const intString = `${intValue}`;
39
+ const lookupDict = this.getLookupDict();
40
+ const key = lookupDict[intString];
41
+ return key;
42
+ }
43
+
44
+ typeSpecificUpdateData(data: ISiStorageData, newValue: keyof T): ISiStorageData {
45
+ const newIntValue = this.dict[newValue];
46
+ return this.intField.typeSpecificUpdateData(data, newIntValue);
47
+ }
48
+ }
@@ -0,0 +1,58 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import { type ISiDataType, type ISiStorageData, ValueToStringError, ValueFromStringError } from './interfaces';
4
+ import { SiDataType } from './SiDataType';
5
+ import { SiFieldValue } from './SiFieldValue';
6
+
7
+ describe('SiFieldValue', () => {
8
+ class MyType extends SiDataType<number> implements ISiDataType<number> {
9
+ typeSpecificIsValueValid(_value: number) {
10
+ return true;
11
+ }
12
+
13
+ typeSpecificValueToString(value: number) {
14
+ if (value === 42) {
15
+ return new ValueToStringError();
16
+ }
17
+ return `${value}`;
18
+ }
19
+
20
+ typeSpecificValueFromString(string: string) {
21
+ const value = parseInt(string, 10);
22
+ return _.isInteger(value) ? value : new ValueFromStringError();
23
+ }
24
+
25
+ typeSpecificExtractFromData(_data: ISiStorageData): number {
26
+ return 1;
27
+ }
28
+
29
+ typeSpecificUpdateData(data: ISiStorageData, _newValue: number): ISiStorageData {
30
+ return data;
31
+ }
32
+ }
33
+ const field = new MyType();
34
+ const myFieldValue = new SiFieldValue(field, 3);
35
+ test('instance', () => {
36
+ expect(myFieldValue.field).toBe(field);
37
+ expect(myFieldValue.value).toBe(3);
38
+ });
39
+ test('toString', () => {
40
+ expect(myFieldValue.toString()).toBe('3');
41
+ });
42
+ test('toString with error', () => {
43
+ const invalidFieldValue = new SiFieldValue(field, 42);
44
+ expect(invalidFieldValue.toString()).toBe('');
45
+ });
46
+ test('fromString with success', () => {
47
+ const myFieldValueFromString = SiFieldValue.fromString(field, '5');
48
+ if (myFieldValueFromString instanceof ValueFromStringError) {
49
+ throw new Error('not expecting an error here');
50
+ }
51
+ expect(myFieldValueFromString.field).toBe(field);
52
+ expect(myFieldValueFromString.value).toBe(5);
53
+ });
54
+ test('fromString with error', () => {
55
+ const myFieldValueFromString = SiFieldValue.fromString(field, 'notanumber');
56
+ expect(myFieldValueFromString instanceof ValueFromStringError).toBe(true);
57
+ });
58
+ });
@@ -0,0 +1,23 @@
1
+ import { ValueToStringError, ValueFromStringError } from './interfaces';
2
+ import type { ISiFieldValue, ISiDataType } from './interfaces';
3
+
4
+ export class SiFieldValue<T> implements ISiFieldValue<T> {
5
+ static fromString<U>(field: ISiDataType<U>, stringValue: string): SiFieldValue<U> | ValueFromStringError {
6
+ const value = field.valueFromString(stringValue);
7
+ if (value instanceof ValueFromStringError) {
8
+ return value;
9
+ }
10
+ return new this(field, value);
11
+ }
12
+
13
+ // eslint-disable-next-line no-useless-constructor
14
+ constructor(
15
+ public field: ISiDataType<T>,
16
+ public value: T // eslint-disable-next-line no-empty-function
17
+ ) {}
18
+
19
+ toString(): string {
20
+ const stringValue = this.field.valueToString(this.value);
21
+ return stringValue instanceof ValueToStringError ? '' : stringValue;
22
+ }
23
+ }
@@ -0,0 +1,80 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import _ from 'lodash';
3
+ import Immutable from 'immutable';
4
+ import { ValueToStringError, ValueFromStringError } from './interfaces';
5
+ import { ModifyUndefinedException } from './SiDataType';
6
+ import { SiFieldValue } from './SiFieldValue';
7
+ import { SiInt } from './SiInt';
8
+
9
+ type FakeSiStorageData = (number | undefined)[];
10
+
11
+ describe('SiInt', () => {
12
+ const mySiInt = new SiInt([[0x00], [0x01, 4, 8]]);
13
+ const fieldValueOf = (intValue: number): SiFieldValue<number> => new SiFieldValue(mySiInt, intValue);
14
+ test('typeSpecificIsValueValid', () => {
15
+ expect(mySiInt.typeSpecificIsValueValid(0)).toBe(true);
16
+ expect(mySiInt.typeSpecificIsValueValid(1)).toBe(true);
17
+ expect(mySiInt.typeSpecificIsValueValid(0xff)).toBe(true);
18
+ expect(mySiInt.typeSpecificIsValueValid(-1)).toBe(false);
19
+ expect(mySiInt.typeSpecificIsValueValid(1.5)).toBe(false);
20
+ expect(mySiInt.typeSpecificIsValueValid(-7.5)).toBe(false);
21
+ });
22
+ test('valueToString', () => {
23
+ expect(mySiInt.valueToString(0)).toBe('0');
24
+ expect(mySiInt.valueToString(1)).toBe('1');
25
+ expect(mySiInt.valueToString(0xff)).toBe('255');
26
+ expect(mySiInt.valueToString(-1) instanceof ValueToStringError).toBe(true);
27
+ });
28
+ test('valueFromString', () => {
29
+ expect(mySiInt.valueFromString('0')).toBe(0);
30
+ expect(mySiInt.valueFromString('1')).toBe(1);
31
+ expect(mySiInt.valueFromString('255')).toBe(0xff);
32
+ expect(mySiInt.valueFromString('0xFF')).toBe(0);
33
+ expect(mySiInt.valueFromString('-1') instanceof ValueFromStringError).toBe(true);
34
+ expect(mySiInt.valueFromString('test') instanceof ValueFromStringError).toBe(true);
35
+ });
36
+ test('extractFromData gives field value', () => {
37
+ const data = Immutable.List([0x00, 0x00]);
38
+ const fieldValue = mySiInt.extractFromData(data);
39
+ expect(fieldValue instanceof SiFieldValue).toBe(true);
40
+ expect(fieldValue!.field).toBe(mySiInt);
41
+ expect(fieldValue!.value).toBe(0);
42
+ });
43
+ test('extractFromData', () => {
44
+ const getExtractedFieldValue = (bytes: FakeSiStorageData) => mySiInt.extractFromData(Immutable.List(bytes));
45
+
46
+ expect(getExtractedFieldValue([0x00, 0x00])!.value).toBe(0x000);
47
+ expect(getExtractedFieldValue([0x0f, 0x00])!.value).toBe(0x00f);
48
+ expect(getExtractedFieldValue([0xff, 0x00])!.value).toBe(0x0ff);
49
+ expect(getExtractedFieldValue([0xff, 0xf0])!.value).toBe(0xfff);
50
+ expect(getExtractedFieldValue([0x00, 0xf0])!.value).toBe(0xf00);
51
+ expect(getExtractedFieldValue([0xab, 0xcd])!.value).toBe(0xcab);
52
+ expect(getExtractedFieldValue([0x00, undefined])).toBe(undefined);
53
+ expect(getExtractedFieldValue([undefined, 0x00])).toBe(undefined);
54
+ expect(getExtractedFieldValue([0x00])).toBe(undefined);
55
+ expect(getExtractedFieldValue([])).toBe(undefined);
56
+ });
57
+ test('updateData', () => {
58
+ const initialData = Immutable.List([0x00, 0x00]);
59
+ const updateInitialData = (newValue: number | SiFieldValue<number>): FakeSiStorageData => mySiInt.updateData(initialData, newValue).toJS();
60
+
61
+ expect(updateInitialData(0x000)).toEqual([0x00, 0x00]);
62
+ expect(updateInitialData(0x00f)).toEqual([0x0f, 0x00]);
63
+ expect(updateInitialData(0x0ff)).toEqual([0xff, 0x00]);
64
+ expect(updateInitialData(0xfff)).toEqual([0xff, 0xf0]);
65
+ expect(updateInitialData(0xf00)).toEqual([0x00, 0xf0]);
66
+ expect(updateInitialData(0xcab)).toEqual([0xab, 0xc0]);
67
+ expect(updateInitialData(fieldValueOf(0x7357))).toEqual([0x57, 0x30]);
68
+ });
69
+ test('updateData modify undefined', () => {
70
+ const updateData = (data: FakeSiStorageData, newValue: number | SiFieldValue<number>): FakeSiStorageData => mySiInt.updateData(Immutable.List(data), newValue).toJS();
71
+
72
+ expect(() => updateData([], 0x000)).toThrow(ModifyUndefinedException);
73
+ expect(() => updateData([], 0xcab)).toThrow(ModifyUndefinedException);
74
+ expect(() => updateData([], fieldValueOf(0x7357))).toThrow(ModifyUndefinedException);
75
+ expect(() => updateData([0x00, undefined], 0xcab)).toThrow(ModifyUndefinedException);
76
+ expect(() => updateData([undefined, 0x00], 0xcab)).toThrow(ModifyUndefinedException);
77
+ expect(() => updateData([0xab, undefined], 0x0ab)).toThrow(ModifyUndefinedException);
78
+ expect(() => updateData([undefined, 0xc0], 0xc00)).toThrow(ModifyUndefinedException);
79
+ });
80
+ });