Added migration for libraryTags
This commit is contained in:
@@ -59,7 +59,7 @@ let LibraryNodeSchema = new SimpleSchema({
|
||||
},
|
||||
libraryTags: {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'libraryTags.$': {
|
||||
@@ -72,12 +72,14 @@ let LibraryNodeSchema = new SimpleSchema({
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
/* TODO: Disabled for now until image upload is working
|
||||
// Image to display when filling the slot
|
||||
slotFillImage: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.url,
|
||||
},
|
||||
*/
|
||||
// Fill more than one quantity in a slot, like feats and ability score
|
||||
// improvements, filtered out of UI if there isn't space in quantityExpected
|
||||
slotQuantityFilled: {
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
// Folders organize a character sheet into a tree, particularly to group things
|
||||
// like 'race' and 'background'
|
||||
let FolderSchema = new createPropertySchema({
|
||||
let FolderSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
groupStats: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
@@ -33,6 +38,19 @@ let FolderSchema = new createPropertySchema({
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyFolderSchema = new createPropertySchema({});
|
||||
const ComputedOnlyFolderSchema = createPropertySchema({
|
||||
summary: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: 'computedOnlyInlineCalculationField',
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
export { FolderSchema, ComputedOnlyFolderSchema };
|
||||
const ComputedFolderSchema = new SimpleSchema()
|
||||
.extend(FolderSchema)
|
||||
.extend(ComputedOnlyFolderSchema);
|
||||
|
||||
export { FolderSchema, ComputedFolderSchema, ComputedOnlyFolderSchema };
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ComputedDamageSchema } from '/imports/api/properties/Damages.js';
|
||||
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
||||
import { ComputedEffectSchema } from '/imports/api/properties/Effects.js';
|
||||
import { ComputedFeatureSchema } from '/imports/api/properties/Features.js';
|
||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||
import { ComputedFolderSchema } from '/imports/api/properties/Folders.js';
|
||||
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
||||
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
||||
import { ComputedPointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||
@@ -43,7 +43,7 @@ const propertySchemasIndex = {
|
||||
damageMultiplier: DamageMultiplierSchema,
|
||||
effect: ComputedEffectSchema,
|
||||
feature: ComputedFeatureSchema,
|
||||
folder: FolderSchema,
|
||||
folder: ComputedFolderSchema,
|
||||
note: ComputedNoteSchema,
|
||||
pointBuy: ComputedPointBuySchema,
|
||||
proficiency: ProficiencySchema,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
const SCHEMA_VERSION = 1;
|
||||
const SCHEMA_VERSION = 2;
|
||||
|
||||
export default SCHEMA_VERSION;
|
||||
|
||||
@@ -10,52 +10,52 @@ import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
Migrations.add({
|
||||
version: 1,
|
||||
name: 'Unifies calculated field schema',
|
||||
up(){
|
||||
up() {
|
||||
migrate();
|
||||
},
|
||||
down(){
|
||||
migrate({reversed: true});
|
||||
down() {
|
||||
migrate({ reversed: true });
|
||||
},
|
||||
});
|
||||
|
||||
function migrate({reversed} = {}){
|
||||
function migrate({ reversed } = {}) {
|
||||
console.log('migrating creature properties');
|
||||
migrateCollection({collection: CreatureProperties, reversed});
|
||||
migrateCollection({ collection: CreatureProperties, reversed });
|
||||
|
||||
console.log('migrating library nodes')
|
||||
migrateCollection({collection: LibraryNodes, reversed});
|
||||
migrateCollection({ collection: LibraryNodes, reversed });
|
||||
}
|
||||
|
||||
function migrateCollection({collection, reversed}){
|
||||
function migrateCollection({ collection, reversed }) {
|
||||
const bulk = collection.rawCollection().initializeUnorderedBulkOp();
|
||||
collection.find({}).forEach(prop => {
|
||||
const newProp = migrateProperty({collection, reversed, prop});
|
||||
const newProp = migrateProperty({ collection, reversed, prop });
|
||||
bulk.find({ _id: prop._id }).replaceOne(newProp);
|
||||
});
|
||||
bulk.execute();
|
||||
}
|
||||
|
||||
export function migrateProperty({collection, reversed, prop}){
|
||||
export function migrateProperty({ collection, reversed, prop }) {
|
||||
const transforms = [
|
||||
...(transformsByPropType[prop.type] || []),
|
||||
{from: 'dependencies'}
|
||||
{ from: 'dependencies' }
|
||||
];
|
||||
let migratedProp = transformFields(prop, transforms, reversed);
|
||||
const schema = collection.simpleSchema({type: migratedProp.type});
|
||||
const schema = collection.simpleSchema({ type: migratedProp.type });
|
||||
// Only clean if the schema version matches our destination version
|
||||
if(!reversed && SCHEMA_VERSION === 1){
|
||||
if (!reversed && SCHEMA_VERSION >= 1) {
|
||||
try {
|
||||
migratedProp = schema.clean(migratedProp);
|
||||
schema.validate(migratedProp);
|
||||
} catch(e){
|
||||
if (e.details[0]?.type === 'maxString'){
|
||||
} catch (e) {
|
||||
if (e.details[0]?.type === 'maxString') {
|
||||
|
||||
console.log({
|
||||
prop: prop,
|
||||
details: e.details,
|
||||
});
|
||||
} else {
|
||||
console.warn({prop, error: e});
|
||||
console.warn({ prop, error: e });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,31 +74,31 @@ const transformsByPropType = {
|
||||
'action': actionTransforms,
|
||||
'adjustment': [
|
||||
...getComputedPropertyTransforms('amount'),
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
{ from: 'target', to: 'target', up: simplifyTarget },
|
||||
],
|
||||
'attack': [
|
||||
...actionTransforms,
|
||||
...getComputedPropertyTransforms('rollBonus', 'attackRoll'),
|
||||
//change type to action
|
||||
{from: 'type', to: 'type', up: () => 'action'},
|
||||
{ from: 'type', to: 'type', up: () => 'action' },
|
||||
],
|
||||
'attribute': [
|
||||
// from: baseValue must be first or else it will delete the field we need
|
||||
{from: 'baseValue', to: 'baseValue.value', up: nanToNull},
|
||||
{from: 'baseValueCalculation', to: 'baseValue.calculation', up: calculationUp, down: calculationDown},
|
||||
{from: 'baseValueErrors', to: 'baseValue.errors', up: trimErrors},
|
||||
{ from: 'baseValue', to: 'baseValue.value', up: nanToNull },
|
||||
{ from: 'baseValueCalculation', to: 'baseValue.calculation', up: calculationUp, down: calculationDown },
|
||||
{ from: 'baseValueErrors', to: 'baseValue.errors', up: trimErrors },
|
||||
...getComputedPropertyTransforms('spellSlotLevel'),
|
||||
...getInlineComputationTransforms('description'),
|
||||
{from: 'value', to: 'total', up: nanToNull},
|
||||
{from: 'currentValue', to: 'value', up: nanToNull},
|
||||
{from: 'proficiency', to: 'proficiency', up: stripZero},
|
||||
{ from: 'value', to: 'total', up: nanToNull },
|
||||
{ from: 'currentValue', to: 'value', up: nanToNull },
|
||||
{ from: 'proficiency', to: 'proficiency', up: stripZero },
|
||||
],
|
||||
'buff': [
|
||||
...getComputedPropertyTransforms('duration'),
|
||||
...getInlineComputationTransforms('description'),
|
||||
{from: 'value', to: 'total', up: nanToNull},
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
{from: 'applied'},
|
||||
{ from: 'value', to: 'total', up: nanToNull },
|
||||
{ from: 'target', to: 'target', up: simplifyTarget },
|
||||
{ from: 'applied' },
|
||||
],
|
||||
'classLevel': [
|
||||
...getInlineComputationTransforms('description'),
|
||||
@@ -108,20 +108,22 @@ const transformsByPropType = {
|
||||
],
|
||||
'damage': [
|
||||
...getComputedPropertyTransforms('amount'),
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
{ from: 'target', to: 'target', up: simplifyTarget },
|
||||
],
|
||||
'effect': [
|
||||
{from: 'calculation', to: 'amount.calculation'},
|
||||
{from: 'result', to: 'amount.value', up: nanToNull},
|
||||
{from: 'errors', to: 'amount.errors', up: trimErrors},
|
||||
{from: 'name', to: 'name', up(val, src, doc){
|
||||
if (src.operation === 'conditional'){
|
||||
doc.text = val;
|
||||
return;
|
||||
} else {
|
||||
return val;
|
||||
{ from: 'calculation', to: 'amount.calculation' },
|
||||
{ from: 'result', to: 'amount.value', up: nanToNull },
|
||||
{ from: 'errors', to: 'amount.errors', up: trimErrors },
|
||||
{
|
||||
from: 'name', to: 'name', up(val, src, doc) {
|
||||
if (src.operation === 'conditional') {
|
||||
doc.text = val;
|
||||
return;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}},
|
||||
},
|
||||
],
|
||||
'feature': [
|
||||
...getInlineComputationTransforms('summary'),
|
||||
@@ -139,20 +141,20 @@ const transformsByPropType = {
|
||||
],
|
||||
'savingThrow': [
|
||||
...getComputedPropertyTransforms('dc'),
|
||||
{from: 'target', to: 'target', up: simplifyTarget},
|
||||
{ from: 'target', to: 'target', up: simplifyTarget },
|
||||
],
|
||||
'skill': [
|
||||
...getComputedPropertyTransforms('baseValue'),
|
||||
...getInlineComputationTransforms('description'),
|
||||
{from: 'value', to: 'value', up: nanToNull},
|
||||
{from: 'passiveBonus', to: 'passiveBonus', up: nanToNull},
|
||||
{from: 'proficiency', to: 'proficiency', up: stripZero},
|
||||
{ from: 'value', to: 'value', up: nanToNull },
|
||||
{ from: 'passiveBonus', to: 'passiveBonus', up: nanToNull },
|
||||
{ from: 'proficiency', to: 'proficiency', up: stripZero },
|
||||
],
|
||||
'spell': [
|
||||
...actionTransforms,
|
||||
],
|
||||
'proficiency': [
|
||||
{from: 'value', to: 'value', up: stripZero},
|
||||
{ from: 'value', to: 'value', up: stripZero },
|
||||
],
|
||||
'propertySlot': [
|
||||
...getComputedPropertyTransforms('quantityExpected'),
|
||||
@@ -166,70 +168,70 @@ const transformsByPropType = {
|
||||
...getInlineComputationTransforms('description'),
|
||||
],
|
||||
'toggle': [
|
||||
{from: 'condition', to: 'condition.calculation'},
|
||||
{from: 'toggleResult', to: 'condition.value', up: nanToNull},
|
||||
{from: 'errors', to: 'condition.errors', up: trimErrors},
|
||||
{ from: 'condition', to: 'condition.calculation' },
|
||||
{ from: 'toggleResult', to: 'condition.value', up: nanToNull },
|
||||
{ from: 'errors', to: 'condition.errors', up: trimErrors },
|
||||
],
|
||||
};
|
||||
|
||||
function getComputedPropertyTransforms(key, toKey){
|
||||
function getComputedPropertyTransforms(key, toKey) {
|
||||
if (!toKey) toKey = key;
|
||||
return [
|
||||
{from: key, to: `${toKey}.calculation`, up: calculationUp, down: calculationDown},
|
||||
{from: `${key}Result`, to: `${toKey}.value`, up: nanToNull},
|
||||
{from: `${key}Errors`, to: `${toKey}.errors`, up: trimErrors},
|
||||
{ from: key, to: `${toKey}.calculation`, up: calculationUp, down: calculationDown },
|
||||
{ from: `${key}Result`, to: `${toKey}.value`, up: nanToNull },
|
||||
{ from: `${key}Errors`, to: `${toKey}.errors`, up: trimErrors },
|
||||
];
|
||||
}
|
||||
|
||||
function getInlineComputationTransforms(key){
|
||||
function getInlineComputationTransforms(key) {
|
||||
return [
|
||||
{from: key, to: `${key}.text`, up: calculationUp, down: calculationDown},
|
||||
{from: `${key}Calculations`, to: `${key}.inlineCalculations`, up: calculationUp, down: calculationDown},
|
||||
{from: `${key}Calculations.$.result`, to: `${key}.inlineCalculations.$.value`},
|
||||
{ from: key, to: `${key}.text`, up: calculationUp, down: calculationDown },
|
||||
{ from: `${key}Calculations`, to: `${key}.inlineCalculations`, up: calculationUp, down: calculationDown },
|
||||
{ from: `${key}Calculations.$.result`, to: `${key}.inlineCalculations.$.value` },
|
||||
];
|
||||
}
|
||||
|
||||
export function calculationUp(val){
|
||||
export function calculationUp(val) {
|
||||
if (typeof val !== 'string') return val;
|
||||
if (!val.replace) console.log({val, replace: val.replace});
|
||||
if (!val.replace) console.log({ val, replace: val.replace });
|
||||
return val.replace(/#(\w+).(\w+)Result/g, '#$1.$2')
|
||||
.replace(/\.value/g, '.total')
|
||||
.replace(/\.currentValue/g, '.value');
|
||||
}
|
||||
|
||||
function calculationDown(val){
|
||||
function calculationDown(val) {
|
||||
if (typeof val !== 'string') return val;
|
||||
return val.replace(/\.value/g, '.currentValue').replace(/\.total/g, '.value');
|
||||
}
|
||||
|
||||
function nanToNull(val){
|
||||
if (Number.isNaN(val)){
|
||||
function nanToNull(val) {
|
||||
if (Number.isNaN(val)) {
|
||||
return null;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function stripZero(val){
|
||||
if (val === 0){
|
||||
function stripZero(val) {
|
||||
if (val === 0) {
|
||||
return undefined;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function simplifyTarget(val){
|
||||
if (val === 'self'){
|
||||
function simplifyTarget(val) {
|
||||
if (val === 'self') {
|
||||
return val;
|
||||
} else {
|
||||
return 'target';
|
||||
}
|
||||
}
|
||||
|
||||
function trimErrors(arr){
|
||||
if(!arr) return arr;
|
||||
function trimErrors(arr) {
|
||||
if (!arr) return arr;
|
||||
arr.forEach(e => {
|
||||
if (e.message.length > STORAGE_LIMITS.errorMessage){
|
||||
if (e.message.length > STORAGE_LIMITS.errorMessage) {
|
||||
e.message = e.message.slice(0, STORAGE_LIMITS.errorMessage);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ const exampleAction = {
|
||||
'ancestors': [{
|
||||
'collection': 'creatures',
|
||||
'id': 'X9rzFhsgFhodYfHmG'
|
||||
}, ],
|
||||
},],
|
||||
'order': 315,
|
||||
'summary': 'Curse a creature for 1 minute. The curse ends early if {warlock.level >14 ? "" : "the target dies, or"} you are incapacitated. \nGain the following benefits: \n- *Bonus to damage rolls against the cursed target of* **+{proficiencyBonus}**. \n- Any attack roll you make against the cursed target is a **critical hit on a roll of 19 or 20**. \n- If the cursed target dies, you **regain {warlock.level+charisma.modifier} hit points**. \n{warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."}',
|
||||
'uses': '1',
|
||||
@@ -45,21 +45,21 @@ const exampleAction = {
|
||||
'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
|
||||
}],
|
||||
'summaryCalculations': [{
|
||||
'calculation': 'warlock.level >14 ? "" : "the target dies, or"',
|
||||
'result': 'the target dies, or'
|
||||
},
|
||||
{
|
||||
'calculation': 'proficiencyBonus',
|
||||
'result': '4'
|
||||
},
|
||||
{
|
||||
'calculation': 'warlock.level+charisma.modifier',
|
||||
'result': '15'
|
||||
},
|
||||
{
|
||||
'calculation': 'warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
|
||||
'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
|
||||
}
|
||||
'calculation': 'warlock.level >14 ? "" : "the target dies, or"',
|
||||
'result': 'the target dies, or'
|
||||
},
|
||||
{
|
||||
'calculation': 'proficiencyBonus',
|
||||
'result': '4'
|
||||
},
|
||||
{
|
||||
'calculation': 'warlock.level+charisma.modifier',
|
||||
'result': '15'
|
||||
},
|
||||
{
|
||||
'calculation': 'warlock.level <9 ? "" : "- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."',
|
||||
'result': '- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses.'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -76,13 +76,13 @@ const exampleAttribute = {
|
||||
'collection': 'creatureProperties'
|
||||
},
|
||||
ancestors: [{
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv'
|
||||
},
|
||||
{
|
||||
'id': '8jSWKxvgQyKbunFtD',
|
||||
'collection': 'creatureProperties'
|
||||
}
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv'
|
||||
},
|
||||
{
|
||||
'id': '8jSWKxvgQyKbunFtD',
|
||||
'collection': 'creatureProperties'
|
||||
}
|
||||
],
|
||||
order: 84,
|
||||
value: 20,
|
||||
@@ -110,13 +110,13 @@ const expectedMigratedAttribute = {
|
||||
'collection': 'creatureProperties'
|
||||
},
|
||||
ancestors: [{
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv'
|
||||
},
|
||||
{
|
||||
'id': '8jSWKxvgQyKbunFtD',
|
||||
'collection': 'creatureProperties'
|
||||
}
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv'
|
||||
},
|
||||
{
|
||||
'id': '8jSWKxvgQyKbunFtD',
|
||||
'collection': 'creatureProperties'
|
||||
}
|
||||
],
|
||||
order: 84,
|
||||
total: 20,
|
||||
@@ -205,11 +205,10 @@ const expectedMigratedAttack = {
|
||||
}],
|
||||
'order': 56,
|
||||
'usesUsed': 2,
|
||||
libraryTags: [],
|
||||
}
|
||||
|
||||
describe('migrateProperty', function() {
|
||||
it('Migrates actions reversibly', function() {
|
||||
describe('migrateProperty', function () {
|
||||
it('Migrates actions reversibly', function () {
|
||||
const action = {
|
||||
...exampleAction
|
||||
};
|
||||
@@ -226,7 +225,7 @@ describe('migrateProperty', function() {
|
||||
assert.deepEqual(action, exampleAction, 'action should not be bashed');
|
||||
assert.deepEqual(exampleAction, reversedAction, 'operation should be reversible');
|
||||
});
|
||||
it('Migrates attributes as expected', function() {
|
||||
it('Migrates attributes as expected', function () {
|
||||
const attribute = {
|
||||
...exampleAttribute
|
||||
};
|
||||
@@ -237,7 +236,7 @@ describe('migrateProperty', function() {
|
||||
assert.deepEqual(newAttribute, expectedMigratedAttribute,
|
||||
'Attribute should match the expected result');
|
||||
});
|
||||
it('Migrates attacks as expected', function() {
|
||||
it('Migrates attacks as expected', function () {
|
||||
const attribute = {
|
||||
...exampleAttack
|
||||
};
|
||||
|
||||
3
app/imports/migrations/server/dbv2/cleanAt2.js
Normal file
3
app/imports/migrations/server/dbv2/cleanAt2.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function cleanAt2() {
|
||||
return;
|
||||
}
|
||||
56
app/imports/migrations/server/dbv2/dbv2.js
Normal file
56
app/imports/migrations/server/dbv2/dbv2.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Migrations } from 'meteor/percolate:migrations';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import { union } from 'lodash';
|
||||
|
||||
// Git version 2.0-beta.33
|
||||
// Database version 1
|
||||
Migrations.add({
|
||||
version: 2,
|
||||
name: 'Separates creature property tags from library tags',
|
||||
|
||||
up() {
|
||||
console.log('migrating up library nodes 1 -> 2');
|
||||
const bulk = LibraryNodes.rawCollection().initializeUnorderedBulkOp();
|
||||
LibraryNodes.find({}).forEach(prop => migratePropUp(bulk, prop));
|
||||
bulk.execute();
|
||||
},
|
||||
|
||||
down() {
|
||||
console.log('migrating down library nodes 2 -> 1');
|
||||
const bulk = LibraryNodes.rawCollection().initializeUnorderedBulkOp();
|
||||
LibraryNodes.find({}).forEach(prop => migratePropDown(bulk, prop));
|
||||
bulk.execute();
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export function migratePropUp(bulk, prop) {
|
||||
// If there are tags, copy them to libraryTags and set findable flags
|
||||
if (Array.isArray(prop.tags) && prop.tags.length) {
|
||||
bulk.find({ _id: prop._id }).updateOne({
|
||||
$set: {
|
||||
libraryTags: prop.tags,
|
||||
fillSlots: true,
|
||||
searchable: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function migratePropDown(bulk, prop) {
|
||||
const update = {
|
||||
$unset: {
|
||||
slotFillImage: 1,
|
||||
slotFillerCondition: 1,
|
||||
libraryTags: 1,
|
||||
fillSlots: 1,
|
||||
searchable: 1,
|
||||
}
|
||||
};
|
||||
if (prop.libraryTags?.length) {
|
||||
update.$set = {
|
||||
tags: union(prop.libraryTags, prop.tags)
|
||||
}
|
||||
}
|
||||
bulk.find({ _id: prop._id }).updateOne(update);
|
||||
}
|
||||
134
app/imports/migrations/server/dbv2/dbv2.test.js
Normal file
134
app/imports/migrations/server/dbv2/dbv2.test.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import { migratePropUp, migratePropDown } from './dbv2.js';
|
||||
import { assert } from 'chai';
|
||||
|
||||
const exampleAttack = {
|
||||
'_id': 'vw23EnJwBRcXEJg7i',
|
||||
'actionType': 'attack',
|
||||
'target': 'singleTarget',
|
||||
'tags': ['attack', 'magical', 'very cool'],
|
||||
'resources': {
|
||||
'itemsConsumed': [],
|
||||
'attributesConsumed': []
|
||||
},
|
||||
'attackRoll': {
|
||||
calculation: 'dexterity.modifier + proficiencyBonus + 2 - hp.total + hp.value',
|
||||
},
|
||||
'type': 'action',
|
||||
'name': 'Claws',
|
||||
'parent': {
|
||||
'id': 'Jpx8q3WjM5SCoGBm8',
|
||||
'collection': 'creatureProperties'
|
||||
},
|
||||
'ancestors': [{
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv'
|
||||
}, {
|
||||
'id': '3WS2xsSPAqB4eF9YH',
|
||||
'collection': 'creatureProperties'
|
||||
}, {
|
||||
'id': 'rhYLEycvtHjcioaQL',
|
||||
'collection': 'creatureProperties'
|
||||
}, {
|
||||
'id': 'Jpx8q3WjM5SCoGBm8',
|
||||
'collection': 'creatureProperties'
|
||||
}],
|
||||
'order': 56,
|
||||
'usesUsed': 2,
|
||||
};
|
||||
|
||||
const expectedAttackUpdate = {
|
||||
$set: {
|
||||
'libraryTags': ['attack', 'magical', 'very cool'],
|
||||
'fillSlots': true,
|
||||
'searchable': true,
|
||||
}
|
||||
};
|
||||
|
||||
const emptyFolderExample = {
|
||||
_id: 'DXPYsHKF6W8Hh3hZs',
|
||||
type: 'folder',
|
||||
name: 'Empty Folder',
|
||||
'parent': {
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv',
|
||||
},
|
||||
'ancestors': [{
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv',
|
||||
}],
|
||||
};
|
||||
|
||||
const DownMergeExample = {
|
||||
_id: 'DXPYsHKF6W8Hh3hZs',
|
||||
type: 'feature',
|
||||
name: 'Feature With Tags and library Tags',
|
||||
'parent': {
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv',
|
||||
},
|
||||
'ancestors': [{
|
||||
'collection': 'creatures',
|
||||
'id': 'm9sdCvs6iDf7qRaGv',
|
||||
}],
|
||||
'libraryTags': ['tags', 'from', 'library'],
|
||||
'tags': ['attack', 'magical', 'very cool'],
|
||||
};
|
||||
|
||||
const expectedDownMergeUpdate = {
|
||||
$unset: {
|
||||
slotFillImage: 1,
|
||||
slotFillerCondition: 1,
|
||||
libraryTags: 1,
|
||||
fillSlots: 1,
|
||||
searchable: 1,
|
||||
},
|
||||
$set: {
|
||||
tags: ['tags', 'from', 'library', 'attack', 'magical', 'very cool'],
|
||||
}
|
||||
};
|
||||
|
||||
describe('dbv2 Migrate library nodes', function () {
|
||||
it('Migrates attacks up', function () {
|
||||
const bulk = stubBulk();
|
||||
migratePropUp(bulk, exampleAttack);
|
||||
const { query, update } = bulk.result();
|
||||
assert.deepEqual(query, { _id: 'vw23EnJwBRcXEJg7i' }, 'The query should match the id of the given prop');
|
||||
assert.deepEqual(update, expectedAttackUpdate, 'The update should match the expected update');
|
||||
});
|
||||
it('Migrates props without tags up', function () {
|
||||
const bulk = stubBulk();
|
||||
migratePropUp(bulk, emptyFolderExample);
|
||||
const { query, update, timesFind, timesUpdate } = bulk.result();
|
||||
assert.isUndefined(query, 'There should be no query on a prop with no tags');
|
||||
assert.equal(timesFind, 0, 'Find should be called zero times on a prop with no tags');
|
||||
assert.isUndefined(update, 'There should be no update on a prop with no tags');
|
||||
assert.equal(timesUpdate, 0, 'Update should be called zero times on a prop with no tags');
|
||||
});
|
||||
it('Merges tags when down migrating', function () {
|
||||
const bulk = stubBulk();
|
||||
migratePropDown(bulk, DownMergeExample);
|
||||
const { query, update } = bulk.result();
|
||||
assert.deepEqual(query, { _id: 'DXPYsHKF6W8Hh3hZs' }, 'The query should match the id of the given prop');
|
||||
assert.deepEqual(update, expectedDownMergeUpdate, 'The update should match the expected update');
|
||||
});
|
||||
});
|
||||
|
||||
// Create a stub for bulk udateOne operations that accepts a single op
|
||||
function stubBulk() {
|
||||
let query, update, timesFind = 0, timesUpdate = 0;
|
||||
return {
|
||||
find(inputQuery) {
|
||||
query = inputQuery;
|
||||
timesFind += 1;
|
||||
return {
|
||||
updateOne(inputUpdate) {
|
||||
update = inputUpdate;
|
||||
timesUpdate += 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
result() {
|
||||
return { query, update, timesFind, timesUpdate }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user