@b2bc-devkit/sheetorm 1.0.3 → 1.2.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.
- package/dist/npm/core/Record.d.ts +17 -0
- package/dist/npm/core/Record.d.ts.map +1 -1
- package/dist/npm/core/Record.js +23 -0
- package/dist/npm/core/Record.js.map +1 -1
- package/dist/npm/core/RecordStatic.d.ts +6 -0
- package/dist/npm/core/RecordStatic.d.ts.map +1 -1
- package/dist/npm/core/Registry.d.ts +1 -0
- package/dist/npm/core/Registry.d.ts.map +1 -1
- package/dist/npm/core/Registry.js +14 -2
- package/dist/npm/core/Registry.js.map +1 -1
- package/dist/npm/core/types/ISpreadsheetAdapter.d.ts +16 -0
- package/dist/npm/core/types/ISpreadsheetAdapter.d.ts.map +1 -1
- package/dist/npm/index.d.ts +5 -3
- package/dist/npm/index.d.ts.map +1 -1
- package/dist/npm/index.js +7 -3
- package/dist/npm/index.js.map +1 -1
- package/dist/npm/storage/GoogleSpreadsheetAdapter.d.ts +16 -0
- package/dist/npm/storage/GoogleSpreadsheetAdapter.d.ts.map +1 -1
- package/dist/npm/storage/GoogleSpreadsheetAdapter.js +32 -0
- package/dist/npm/storage/GoogleSpreadsheetAdapter.js.map +1 -1
- package/dist/npm/testing/ParityCatalog.d.ts.map +1 -1
- package/dist/npm/testing/ParityCatalog.js +7 -0
- package/dist/npm/testing/ParityCatalog.js.map +1 -1
- package/dist/npm/testing/RuntimeParity.d.ts +10 -6
- package/dist/npm/testing/RuntimeParity.d.ts.map +1 -1
- package/dist/npm/testing/RuntimeParity.js +170 -7
- package/dist/npm/testing/RuntimeParity.js.map +1 -1
- package/package.json +4 -4
- package/{README.md → readme.md} +91 -14
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
* every Jest `test()` block. Each handler exercises the same logic but using
|
|
15
15
|
* real Google Sheets API calls instead of mocks.
|
|
16
16
|
* - {@link RuntimeParity} — public API (static methods) invoked from GAS menu:
|
|
17
|
-
* - `runStageOne()` / `runStageTwo()` / `runStageThree()` — execute test subsets.
|
|
17
|
+
* - `runStageOne()` / `runStageTwo()` / `runStageThree()` / `runStageFour()` — execute test subsets.
|
|
18
18
|
* - `validate()` — cross-check collected results against {@link ParityCatalog}.
|
|
19
19
|
*
|
|
20
|
-
* Tests are split into
|
|
20
|
+
* Tests are split into four stages to stay within the 6-minute GAS execution
|
|
21
21
|
* time limit. Each stage persists results to a `__parity_results_*` sheet so
|
|
22
22
|
* that `validate()` can aggregate them later.
|
|
23
23
|
*
|
|
@@ -2975,6 +2975,161 @@ const runtimeSuiteHandlers = {
|
|
|
2975
2975
|
}
|
|
2976
2976
|
assertTrue(threw, "save() should throw when indexed schema has no indexTableName");
|
|
2977
2977
|
},
|
|
2978
|
+
"protects sheet on first save when isProtected returns true": (ctx) => {
|
|
2979
|
+
const adapter = ctx.state.getAdapter();
|
|
2980
|
+
const suffix = ctx.state.nextTableName("prot");
|
|
2981
|
+
Registry.reset();
|
|
2982
|
+
resetDecoratorCaches();
|
|
2983
|
+
Registry.getInstance().configure({ adapter });
|
|
2984
|
+
class ProtectedNote extends BaseRecord {
|
|
2985
|
+
static get tableName() {
|
|
2986
|
+
return `ProtNote_${suffix}`;
|
|
2987
|
+
}
|
|
2988
|
+
static isProtected() {
|
|
2989
|
+
return true;
|
|
2990
|
+
}
|
|
2991
|
+
static protectedFor() {
|
|
2992
|
+
return ["alice@example.com", "bob@example.com"];
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
const note = new ProtectedNote();
|
|
2996
|
+
note.body = "secret";
|
|
2997
|
+
note.save();
|
|
2998
|
+
// Verify the sheet was created and protected
|
|
2999
|
+
const spreadsheet = ctx.state.getSpreadsheet();
|
|
3000
|
+
const sheet = spreadsheet.getSheetByName(`ProtNote_${suffix}`);
|
|
3001
|
+
assertTrue(sheet !== null, "protected sheet should exist");
|
|
3002
|
+
const protections = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET);
|
|
3003
|
+
assertTrue(protections.length > 0, "sheet should have at least one protection");
|
|
3004
|
+
const editors = protections[0].getEditors().map((e) => e.getEmail());
|
|
3005
|
+
assertTrue(editors.includes("alice@example.com"), "alice should be an editor");
|
|
3006
|
+
assertTrue(editors.includes("bob@example.com"), "bob should be an editor");
|
|
3007
|
+
},
|
|
3008
|
+
"does not protect sheet when isProtected returns false": (ctx) => {
|
|
3009
|
+
const { Car } = setup(ctx);
|
|
3010
|
+
const car = new Car();
|
|
3011
|
+
car.make = "Toyota";
|
|
3012
|
+
car.model = "Corolla";
|
|
3013
|
+
car.save();
|
|
3014
|
+
const spreadsheet = ctx.state.getSpreadsheet();
|
|
3015
|
+
const sheet = spreadsheet.getSheetByName(Car.tableName);
|
|
3016
|
+
assertTrue(sheet !== null, "sheet should exist");
|
|
3017
|
+
const protections = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET);
|
|
3018
|
+
assertEqual(protections.length, 0, "unprotected sheet should have no protections");
|
|
3019
|
+
},
|
|
3020
|
+
"does not re-protect sheet on subsequent saves": (ctx) => {
|
|
3021
|
+
const adapter = ctx.state.getAdapter();
|
|
3022
|
+
const suffix = ctx.state.nextTableName("reprot");
|
|
3023
|
+
Registry.reset();
|
|
3024
|
+
resetDecoratorCaches();
|
|
3025
|
+
Registry.getInstance().configure({ adapter });
|
|
3026
|
+
class TrackedProtected extends BaseRecord {
|
|
3027
|
+
static get tableName() {
|
|
3028
|
+
return `Tracked_${suffix}`;
|
|
3029
|
+
}
|
|
3030
|
+
static isProtected() {
|
|
3031
|
+
return true;
|
|
3032
|
+
}
|
|
3033
|
+
static protectedFor() {
|
|
3034
|
+
return ["admin@example.com"];
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const item1 = new TrackedProtected();
|
|
3038
|
+
item1.value = "first";
|
|
3039
|
+
item1.save();
|
|
3040
|
+
const spreadsheet = ctx.state.getSpreadsheet();
|
|
3041
|
+
const sheet = spreadsheet.getSheetByName(`Tracked_${suffix}`);
|
|
3042
|
+
assertTrue(sheet !== null, "sheet should exist");
|
|
3043
|
+
const protsBefore = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET);
|
|
3044
|
+
assertEqual(protsBefore.length, 1, "should have exactly one protection after first save");
|
|
3045
|
+
const item2 = new TrackedProtected();
|
|
3046
|
+
item2.value = "second";
|
|
3047
|
+
item2.save();
|
|
3048
|
+
const protsAfter = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET);
|
|
3049
|
+
assertEqual(protsAfter.length, 1, "should still have exactly one protection after second save");
|
|
3050
|
+
},
|
|
3051
|
+
"protects sheet with empty editors list": (ctx) => {
|
|
3052
|
+
const adapter = ctx.state.getAdapter();
|
|
3053
|
+
const suffix = ctx.state.nextTableName("locked");
|
|
3054
|
+
Registry.reset();
|
|
3055
|
+
resetDecoratorCaches();
|
|
3056
|
+
Registry.getInstance().configure({ adapter });
|
|
3057
|
+
class LockedSheet extends BaseRecord {
|
|
3058
|
+
static get tableName() {
|
|
3059
|
+
return `Locked_${suffix}`;
|
|
3060
|
+
}
|
|
3061
|
+
static isProtected() {
|
|
3062
|
+
return true;
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
const item = new LockedSheet();
|
|
3066
|
+
item.data = "locked";
|
|
3067
|
+
item.save();
|
|
3068
|
+
const spreadsheet = ctx.state.getSpreadsheet();
|
|
3069
|
+
const sheet = spreadsheet.getSheetByName(`Locked_${suffix}`);
|
|
3070
|
+
assertTrue(sheet !== null, "sheet should exist");
|
|
3071
|
+
const protections = sheet.getProtections(SpreadsheetApp.ProtectionType.SHEET);
|
|
3072
|
+
assertTrue(protections.length > 0, "sheet should be protected even with no editors");
|
|
3073
|
+
},
|
|
3074
|
+
"hides sheet on first save when isHidden returns true": (ctx) => {
|
|
3075
|
+
const adapter = ctx.state.getAdapter();
|
|
3076
|
+
const suffix = ctx.state.nextTableName("hidden");
|
|
3077
|
+
Registry.reset();
|
|
3078
|
+
resetDecoratorCaches();
|
|
3079
|
+
Registry.getInstance().configure({ adapter });
|
|
3080
|
+
class HiddenLog extends BaseRecord {
|
|
3081
|
+
static get tableName() {
|
|
3082
|
+
return `HidLog_${suffix}`;
|
|
3083
|
+
}
|
|
3084
|
+
static isHidden() {
|
|
3085
|
+
return true;
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
const log = new HiddenLog();
|
|
3089
|
+
log.entry = "secret log";
|
|
3090
|
+
log.save();
|
|
3091
|
+
const spreadsheet = ctx.state.getSpreadsheet();
|
|
3092
|
+
const sheet = spreadsheet.getSheetByName(`HidLog_${suffix}`);
|
|
3093
|
+
assertTrue(sheet !== null, "hidden sheet should exist");
|
|
3094
|
+
assertTrue(sheet.isSheetHidden(), "sheet should be hidden");
|
|
3095
|
+
},
|
|
3096
|
+
"does not hide sheet when isHidden returns false": (ctx) => {
|
|
3097
|
+
const { Car } = setup(ctx);
|
|
3098
|
+
const car = new Car();
|
|
3099
|
+
car.make = "Toyota";
|
|
3100
|
+
car.model = "Corolla";
|
|
3101
|
+
car.save();
|
|
3102
|
+
const spreadsheet = ctx.state.getSpreadsheet();
|
|
3103
|
+
const sheet = spreadsheet.getSheetByName(Car.tableName);
|
|
3104
|
+
assertTrue(sheet !== null, "sheet should exist");
|
|
3105
|
+
assertTrue(!sheet.isSheetHidden(), "sheet should not be hidden");
|
|
3106
|
+
},
|
|
3107
|
+
"does not re-hide sheet on subsequent saves": (ctx) => {
|
|
3108
|
+
const adapter = ctx.state.getAdapter();
|
|
3109
|
+
const suffix = ctx.state.nextTableName("rehid");
|
|
3110
|
+
Registry.reset();
|
|
3111
|
+
resetDecoratorCaches();
|
|
3112
|
+
Registry.getInstance().configure({ adapter });
|
|
3113
|
+
class TrackedHidden extends BaseRecord {
|
|
3114
|
+
static get tableName() {
|
|
3115
|
+
return `TrkHid_${suffix}`;
|
|
3116
|
+
}
|
|
3117
|
+
static isHidden() {
|
|
3118
|
+
return true;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
const item1 = new TrackedHidden();
|
|
3122
|
+
item1.value = "first";
|
|
3123
|
+
item1.save();
|
|
3124
|
+
const spreadsheet = ctx.state.getSpreadsheet();
|
|
3125
|
+
const sheet = spreadsheet.getSheetByName(`TrkHid_${suffix}`);
|
|
3126
|
+
assertTrue(sheet !== null, "sheet should exist");
|
|
3127
|
+
assertTrue(sheet.isSheetHidden(), "sheet should be hidden after first save");
|
|
3128
|
+
const item2 = new TrackedHidden();
|
|
3129
|
+
item2.value = "second";
|
|
3130
|
+
item2.save();
|
|
3131
|
+
assertTrue(sheet.isSheetHidden(), "sheet should still be hidden after second save");
|
|
3132
|
+
},
|
|
2978
3133
|
};
|
|
2979
3134
|
})(),
|
|
2980
3135
|
"sheet-repository.test.ts": {
|
|
@@ -3996,14 +4151,20 @@ function runTestsStageOne() {
|
|
|
3996
4151
|
validateTests();
|
|
3997
4152
|
return runTestsForSuites(ParityCatalog.SUITES.slice(0, 4));
|
|
3998
4153
|
}
|
|
3999
|
-
/** Stage 2: serialization, uuid
|
|
4154
|
+
/** Stage 2: serialization, uuid (~52 tests, ~60s). */
|
|
4000
4155
|
function runTestsStageTwo() {
|
|
4001
4156
|
SheetOrmLogger.verbose = false;
|
|
4002
4157
|
validateTests();
|
|
4003
|
-
return runTestsForSuites(ParityCatalog.SUITES.slice(4,
|
|
4158
|
+
return runTestsForSuites(ParityCatalog.SUITES.slice(4, 6));
|
|
4004
4159
|
}
|
|
4005
|
-
/** Stage 3:
|
|
4160
|
+
/** Stage 3: record (~81 tests, ~250s). */
|
|
4006
4161
|
function runTestsStageThree() {
|
|
4162
|
+
SheetOrmLogger.verbose = false;
|
|
4163
|
+
validateTests();
|
|
4164
|
+
return runTestsForSuites(ParityCatalog.SUITES.slice(6, 7));
|
|
4165
|
+
}
|
|
4166
|
+
/** Stage 4: sheet-repository (~35 tests, ~120s). */
|
|
4167
|
+
function runTestsStageFour() {
|
|
4007
4168
|
SheetOrmLogger.verbose = false;
|
|
4008
4169
|
validateTests();
|
|
4009
4170
|
return runTestsForSuites(ParityCatalog.SUITES.slice(7));
|
|
@@ -4019,10 +4180,12 @@ export class RuntimeParity {
|
|
|
4019
4180
|
}
|
|
4020
4181
|
/** Execute stage-one parity tests (cache, index-store, query, query-engine). */
|
|
4021
4182
|
RuntimeParity.runStageOne = runTestsStageOne;
|
|
4022
|
-
/** Execute stage-two parity tests (serialization, uuid
|
|
4183
|
+
/** Execute stage-two parity tests (serialization, uuid). */
|
|
4023
4184
|
RuntimeParity.runStageTwo = runTestsStageTwo;
|
|
4024
|
-
/** Execute stage-three parity tests (
|
|
4185
|
+
/** Execute stage-three parity tests (record). */
|
|
4025
4186
|
RuntimeParity.runStageThree = runTestsStageThree;
|
|
4187
|
+
/** Execute stage-four parity tests (sheet-repository). */
|
|
4188
|
+
RuntimeParity.runStageFour = runTestsStageFour;
|
|
4026
4189
|
/** Cross-check that all ParityCatalog case IDs have runtime handlers. */
|
|
4027
4190
|
RuntimeParity.validate = validateTests;
|
|
4028
4191
|
/** Complete list of runtime parity case IDs (for external cross-reference). */
|