Add feature decorator system #13
16 changed files with 14621 additions and 89 deletions
|
@ -12,15 +12,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- **BREAKING** Formulas, which can be used to calculate buy max for you
|
- **BREAKING** Formulas, which can be used to calculate buy max for you
|
||||||
- Requirements can use them so repeatables and challenges can be "buy max" without any extra effort
|
- Requirements can use them so repeatables and challenges can be "buy max" without any extra effort
|
||||||
- Conversions now use formulas instead of the old scaling functions system, allowing for arbitrary functions that are much easier to follow
|
- Conversions now use formulas instead of the old scaling functions system, allowing for arbitrary functions that are much easier to follow
|
||||||
- There's a utility for converting modifiers to formulas, thus replacing things like the gain modifier on conversions
|
- Modifiers have a new getFormula property
|
||||||
- Feature decorators, which simplify the process of adding extra values to features
|
- Feature decorators, which simplify the process of adding extra values to features
|
||||||
- Action feature, which is a clickable with a cooldown
|
- Action feature, which is a clickable with a cooldown
|
||||||
- ETA util (calculates time until a specific amount of a resource, based on its current gain rate)
|
- ETA util (calculates time until a specific amount of a resource, based on its current gain rate)
|
||||||
- createCollapsibleMilestones util
|
- createCollapsibleAchievements util
|
||||||
- deleteLowerSaves util
|
- deleteLowerSaves util
|
||||||
- Minimized layers can now display a component
|
- Minimized layers can now display a component
|
||||||
- submitOnBlur property to Text fields
|
- submitOnBlur property to Text fields
|
||||||
- showPopups property to Milestones
|
- showPopups property to achievements
|
||||||
- Mouse/touch events to more onClick listeners
|
- Mouse/touch events to more onClick listeners
|
||||||
- Example hotkey to starting layer
|
- Example hotkey to starting layer
|
||||||
- Schema for projInfo.json
|
- Schema for projInfo.json
|
||||||
|
@ -72,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Tests
|
### Tests
|
||||||
- conversions
|
- conversions
|
||||||
- formulas
|
- formulas
|
||||||
|
- modifiers
|
||||||
- requirements
|
- requirements
|
||||||
|
|
||||||
Contributors: thepaperpilot, escapee, adsaf, ducdat
|
Contributors: thepaperpilot, escapee, adsaf, ducdat
|
||||||
|
|
16
package-lock.json
generated
16
package-lock.json
generated
|
@ -43,7 +43,7 @@
|
||||||
"eslint": "^8.6.0",
|
"eslint": "^8.6.0",
|
||||||
"jsdom": "^20.0.0",
|
"jsdom": "^20.0.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^5.0.2",
|
||||||
"vitest": "^0.29.3",
|
"vitest": "^0.29.3",
|
||||||
"vue-tsc": "^0.38.1"
|
"vue-tsc": "^0.38.1"
|
||||||
},
|
},
|
||||||
|
@ -7005,16 +7005,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.7.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.0"
|
"node": ">=12.20"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ufo": {
|
"node_modules/ufo": {
|
||||||
|
@ -12971,9 +12971,9 @@
|
||||||
"integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="
|
"integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.7.4",
|
"version": "5.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ufo": {
|
"ufo": {
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
"eslint": "^8.6.0",
|
"eslint": "^8.6.0",
|
||||||
"jsdom": "^20.0.0",
|
"jsdom": "^20.0.0",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^5.0.2",
|
||||||
"vitest": "^0.29.3",
|
"vitest": "^0.29.3",
|
||||||
"vue-tsc": "^0.38.1"
|
"vue-tsc": "^0.38.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
v-if="layerKeys.includes(tab)"
|
v-if="layerKeys.includes(tab)"
|
||||||
v-bind="gatherLayerProps(layers[tab]!)"
|
v-bind="gatherLayerProps(layers[tab]!)"
|
||||||
:index="index"
|
:index="index"
|
||||||
@set-minimized="value => (layers[tab]!.minimized.value = value)"
|
@set-minimized="(value: boolean) => (layers[tab]!.minimized.value = value)"
|
||||||
/>
|
/>
|
||||||
<component :is="tab" :index="index" v-else />
|
<component :is="tab" :index="index" v-else />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -73,10 +73,6 @@ export default defineComponent({
|
||||||
player.tabs.splice(unref(props.index), Infinity);
|
player.tabs.splice(unref(props.index), Infinity);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setMinimized(min: boolean) {
|
|
||||||
minimized.value = min;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNodes(nodes: Record<string, FeatureNode | undefined>) {
|
function updateNodes(nodes: Record<string, FeatureNode | undefined>) {
|
||||||
props.nodes.value = nodes;
|
props.nodes.value = nodes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<DangerButton
|
<DangerButton
|
||||||
:disabled="isActive"
|
:disabled="isActive"
|
||||||
@click="emit('delete')"
|
@click="emit('delete')"
|
||||||
@confirmingChanged="value => (isConfirming = value)"
|
@confirmingChanged="(value: boolean) => (isConfirming = value)"
|
||||||
>
|
>
|
||||||
<Tooltip display="Delete" :direction="Direction.Left" class="info">
|
<Tooltip display="Delete" :direction="Direction.Left" class="info">
|
||||||
<span class="material-icons" style="margin: -2px">delete</span>
|
<span class="material-icons" style="margin: -2px">delete</span>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
:save="saves[element]"
|
:save="saves[element]"
|
||||||
@open="openSave(element)"
|
@open="openSave(element)"
|
||||||
@export="exportSave(element)"
|
@export="exportSave(element)"
|
||||||
@editName="name => editSave(element, name)"
|
@editName="(name: string) => editSave(element, name)"
|
||||||
@duplicate="duplicateSave(element)"
|
@duplicate="duplicateSave(element)"
|
||||||
@delete="deleteSave(element)"
|
@delete="deleteSave(element)"
|
||||||
/>
|
/>
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
v-if="Object.keys(bank).length > 0"
|
v-if="Object.keys(bank).length > 0"
|
||||||
:options="bank"
|
:options="bank"
|
||||||
:modelValue="selectedPreset"
|
:modelValue="selectedPreset"
|
||||||
@update:modelValue="preset => newFromPreset(preset as string)"
|
@update:modelValue="(preset: unknown) => newFromPreset(preset as string)"
|
||||||
closeOnSelect
|
closeOnSelect
|
||||||
placeholder="Select preset"
|
placeholder="Select preset"
|
||||||
class="presets"
|
class="presets"
|
||||||
|
|
|
@ -253,17 +253,17 @@ export interface Section {
|
||||||
baseText?: Computable<CoercableComponent>;
|
baseText?: Computable<CoercableComponent>;
|
||||||
/** Whether or not this section should be currently visible to the player. **/
|
/** Whether or not this section should be currently visible to the player. **/
|
||||||
visible?: Computable<boolean>;
|
visible?: Computable<boolean>;
|
||||||
|
/** Determines if numbers larger or smaller than the base should be displayed as red. */
|
||||||
|
smallerIsBetter?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes an array of modifier "sections", and creates a JSXFunction that can render all those sections, and allow each section to be collapsed.
|
* Takes an array of modifier "sections", and creates a JSXFunction that can render all those sections, and allow each section to be collapsed.
|
||||||
* Also returns a list of persistent refs that are used to control which sections are currently collapsed.
|
* Also returns a list of persistent refs that are used to control which sections are currently collapsed.
|
||||||
* @param sectionsFunc A function that returns the sections to display.
|
* @param sectionsFunc A function that returns the sections to display.
|
||||||
* @param smallerIsBetter Determines whether numbers larger or smaller than the base should be displayed as red.
|
|
||||||
*/
|
*/
|
||||||
export function createCollapsibleModifierSections(
|
export function createCollapsibleModifierSections(
|
||||||
sectionsFunc: () => Section[],
|
sectionsFunc: () => Section[]
|
||||||
smallerIsBetter = false
|
|
||||||
): [JSXFunction, Persistent<Record<number, boolean>>] {
|
): [JSXFunction, Persistent<Record<number, boolean>>] {
|
||||||
const sections: Section[] = [];
|
const sections: Section[] = [];
|
||||||
const processed:
|
const processed:
|
||||||
|
@ -324,7 +324,9 @@ export function createCollapsibleModifierSections(
|
||||||
{s.unit}
|
{s.unit}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{renderJSX(unref(s.modifier.description))}
|
{s.modifier.description == null
|
||||||
|
? null
|
||||||
|
: renderJSX(unref(s.modifier.description))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -353,7 +355,7 @@ export function createCollapsibleModifierSections(
|
||||||
class="modifier-amount"
|
class="modifier-amount"
|
||||||
style={
|
style={
|
||||||
(
|
(
|
||||||
smallerIsBetter === true
|
s.smallerIsBetter === true
|
||||||
? Decimal.gt(total, base ?? 1)
|
? Decimal.gt(total, base ?? 1)
|
||||||
: Decimal.lt(total, base ?? 1)
|
: Decimal.lt(total, base ?? 1)
|
||||||
)
|
)
|
||||||
|
@ -491,29 +493,3 @@ export function createFormulaPreview(
|
||||||
return formatSmall(formula.evaluate());
|
return formatSmall(formula.evaluate());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility for converting a modifier into a formula. Takes the input for this formula as the base parameter.
|
|
||||||
* @param modifier The modifier to convert to the formula
|
|
||||||
* @param base An existing formula or processed DecimalSource that will be the input to the formula
|
|
||||||
*/
|
|
||||||
export function modifierToFormula<T extends GenericFormula>(
|
|
||||||
modifier: WithRequired<Modifier, "revert">,
|
|
||||||
base: T
|
|
||||||
): T;
|
|
||||||
export function modifierToFormula(modifier: Modifier, base: FormulaSource): GenericFormula;
|
|
||||||
export function modifierToFormula(modifier: Modifier, base: FormulaSource) {
|
|
||||||
return new Formula({
|
|
||||||
inputs: [base],
|
|
||||||
evaluate: val => modifier.apply(val),
|
|
||||||
invert:
|
|
||||||
"revert" in modifier && modifier.revert != null
|
|
||||||
? (val, lhs) => {
|
|
||||||
if (lhs instanceof Formula && lhs.hasVariable()) {
|
|
||||||
return lhs.invert(modifier.revert!(val));
|
|
||||||
}
|
|
||||||
throw new Error("Could not invert due to no input being a variable");
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ import { Decorator } from "./decorators/common";
|
||||||
export const ActionType = Symbol("Action");
|
export const ActionType = Symbol("Action");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that configures a {@link Action}.
|
* An object that configures an {@link Action}.
|
||||||
*/
|
*/
|
||||||
export interface ActionOptions extends Omit<ClickableOptions, "onClick" | "onHold"> {
|
export interface ActionOptions extends Omit<ClickableOptions, "onClick" | "onHold"> {
|
||||||
/** The cooldown during which the action cannot be performed again, in seconds. */
|
/** The cooldown during which the action cannot be performed again, in seconds. */
|
||||||
|
@ -72,7 +72,7 @@ export interface BaseAction {
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that represens a feature that can be clicked upon, and then have a cooldown before they can be clicked again. */
|
/** An object that represents a feature that can be clicked upon, and then has a cooldown before it can be clicked again. */
|
||||||
export type Action<T extends ActionOptions> = Replace<
|
export type Action<T extends ActionOptions> = Replace<
|
||||||
T & BaseAction,
|
T & BaseAction,
|
||||||
{
|
{
|
||||||
|
|
|
@ -170,7 +170,7 @@
|
||||||
import themes from "data/themes";
|
import themes from "data/themes";
|
||||||
import type { BoardNode, GenericBoardNodeAction, GenericNodeType } from "features/boards/board";
|
import type { BoardNode, GenericBoardNodeAction, GenericNodeType } from "features/boards/board";
|
||||||
import { ProgressDisplay, getNodeProperty, Shape } from "features/boards/board";
|
import { ProgressDisplay, getNodeProperty, Shape } from "features/boards/board";
|
||||||
import { Visibility } from "features/feature";
|
import { isVisible } from "features/feature";
|
||||||
import settings from "game/settings";
|
import settings from "game/settings";
|
||||||
import { computed, ref, toRefs, unref, watch } from "vue";
|
import { computed, ref, toRefs, unref, watch } from "vue";
|
||||||
|
|
||||||
|
@ -210,8 +210,8 @@ watch(isDraggable, value => {
|
||||||
|
|
||||||
const actions = computed(() => {
|
const actions = computed(() => {
|
||||||
const node = unref(props.node);
|
const node = unref(props.node);
|
||||||
return getNodeProperty(props.nodeType.value.actions, node)?.filter(
|
return getNodeProperty(props.nodeType.value.actions, node)?.filter(action =>
|
||||||
action => getNodeProperty(action.visibility, node) !== Visibility.None
|
isVisible(getNodeProperty(action.visibility, node))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ export function createParticles<T extends ParticlesOptions>(
|
||||||
const particles = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
const particles = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
particles.id = getUniqueID("particles-");
|
particles.id = getUniqueID("particles-");
|
||||||
particles.type = ParticlesType;
|
particles.type = ParticlesType;
|
||||||
particles[Component] = ParticlesComponent;
|
particles[Component] = ParticlesComponent as GenericComponent;
|
||||||
|
|
||||||
particles.app = shallowRef(null);
|
particles.app = shallowRef(null);
|
||||||
particles.addEmitter = (config: EmitterConfigV3): Promise<Emitter> => {
|
particles.addEmitter = (config: EmitterConfigV3): Promise<Emitter> => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CoercableComponent, Replace, StyleValue } from "features/feature";
|
import type { CoercableComponent, GenericComponent, Replace, StyleValue } from "features/feature";
|
||||||
import { Component, GatherProps, setDefault } from "features/feature";
|
import { Component, GatherProps, setDefault } from "features/feature";
|
||||||
import { deletePersistent, Persistent, persistent } from "game/persistence";
|
import { deletePersistent, Persistent, persistent } from "game/persistence";
|
||||||
import { Direction } from "util/common";
|
import { Direction } from "util/common";
|
||||||
|
@ -76,7 +76,7 @@ export type GenericTooltip = Replace<
|
||||||
/**
|
/**
|
||||||
* Creates a tooltip on the given element with the given options.
|
* Creates a tooltip on the given element with the given options.
|
||||||
* @param element The renderable feature to display the tooltip on.
|
* @param element The renderable feature to display the tooltip on.
|
||||||
* @param optionsFunc Clickable options.
|
* @param options Tooltip options.
|
||||||
*/
|
*/
|
||||||
export function addTooltip<T extends TooltipOptions>(
|
export function addTooltip<T extends TooltipOptions>(
|
||||||
element: VueFeature,
|
element: VueFeature,
|
||||||
|
@ -108,7 +108,7 @@ export function addTooltip<T extends TooltipOptions>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const elementComponent = element[Component];
|
const elementComponent = element[Component];
|
||||||
element[Component] = TooltipComponent;
|
element[Component] = TooltipComponent as GenericComponent;
|
||||||
const elementGatherProps = element[GatherProps].bind(element);
|
const elementGatherProps = element[GatherProps].bind(element);
|
||||||
element[GatherProps] = function gatherTooltipProps(this: GenericTooltip) {
|
element[GatherProps] = function gatherTooltipProps(this: GenericTooltip) {
|
||||||
const { display, classes, style, direction, xoffset, yoffset, pinned } = this;
|
const { display, classes, style, direction, xoffset, yoffset, pinned } = this;
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { convertComputable } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { renderJSX } from "util/vue";
|
import { renderJSX } from "util/vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
|
import Formula from "./formulas/formulas";
|
||||||
|
import { FormulaSource, GenericFormula } from "./formulas/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that can be used to apply or unapply some modification to a number.
|
* An object that can be used to apply or unapply some modification to a number.
|
||||||
|
@ -21,7 +23,9 @@ export interface Modifier {
|
||||||
/** Applies some operation on the input and returns the result. */
|
/** Applies some operation on the input and returns the result. */
|
||||||
apply: (gain: DecimalSource) => DecimalSource;
|
apply: (gain: DecimalSource) => DecimalSource;
|
||||||
/** Reverses the operation applied by the apply property. Required by some features. */
|
/** Reverses the operation applied by the apply property. Required by some features. */
|
||||||
revert?: (gain: DecimalSource) => DecimalSource;
|
invert?: (gain: DecimalSource) => DecimalSource;
|
||||||
|
/** Get a formula for this modifier. Required by some features. */
|
||||||
|
getFormula?: (gain: FormulaSource) => GenericFormula;
|
||||||
/**
|
/**
|
||||||
* Whether or not this modifier should be considered enabled.
|
* Whether or not this modifier should be considered enabled.
|
||||||
* Typically for use with modifiers passed into {@link createSequentialModifier}.
|
* Typically for use with modifiers passed into {@link createSequentialModifier}.
|
||||||
|
@ -39,20 +43,20 @@ export interface Modifier {
|
||||||
*/
|
*/
|
||||||
export type ModifierFromOptionalParams<T, S> = T extends undefined
|
export type ModifierFromOptionalParams<T, S> = T extends undefined
|
||||||
? S extends undefined
|
? S extends undefined
|
||||||
? Omit<WithRequired<Modifier, "revert">, "description" | "enabled">
|
? Omit<WithRequired<Modifier, "invert" | "getFormula">, "description" | "enabled">
|
||||||
: Omit<WithRequired<Modifier, "revert" | "enabled">, "description">
|
: Omit<WithRequired<Modifier, "invert" | "enabled" | "getFormula">, "description">
|
||||||
: S extends undefined
|
: S extends undefined
|
||||||
? Omit<WithRequired<Modifier, "revert" | "description">, "enabled">
|
? Omit<WithRequired<Modifier, "invert" | "description" | "getFormula">, "enabled">
|
||||||
: WithRequired<Modifier, "revert" | "enabled" | "description">;
|
: WithRequired<Modifier, "invert" | "enabled" | "description" | "getFormula">;
|
||||||
|
|
||||||
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
|
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
|
||||||
export interface AdditiveModifierOptions {
|
export interface AdditiveModifierOptions {
|
||||||
/** The amount to add to the input value. */
|
/** The amount to add to the input value. */
|
||||||
addend: Computable<DecimalSource>;
|
addend: Computable<DecimalSource>;
|
||||||
/** Description of what this modifier is doing. */
|
/** Description of what this modifier is doing. */
|
||||||
description?: Computable<CoercableComponent> | undefined;
|
description?: Computable<CoercableComponent>;
|
||||||
/** A computable that will be processed and passed directly into the returned modifier. */
|
/** A computable that will be processed and passed directly into the returned modifier. */
|
||||||
enabled?: Computable<boolean> | undefined;
|
enabled?: Computable<boolean>;
|
||||||
/** Determines if numbers larger or smaller than 0 should be displayed as red. */
|
/** Determines if numbers larger or smaller than 0 should be displayed as red. */
|
||||||
smallerIsBetter?: boolean;
|
smallerIsBetter?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +76,8 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions>(
|
||||||
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
|
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
|
||||||
return {
|
return {
|
||||||
apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
|
apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
|
||||||
revert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)),
|
invert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)),
|
||||||
|
getFormula: (gain: FormulaSource) => Formula.add(gain, processedAddend),
|
||||||
enabled: processedEnabled,
|
enabled: processedEnabled,
|
||||||
description:
|
description:
|
||||||
description == null
|
description == null
|
||||||
|
@ -133,7 +138,8 @@ export function createMultiplicativeModifier<T extends MultiplicativeModifierOpt
|
||||||
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
|
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
|
||||||
return {
|
return {
|
||||||
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
|
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
|
||||||
revert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)),
|
invert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)),
|
||||||
|
getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier),
|
||||||
enabled: processedEnabled,
|
enabled: processedEnabled,
|
||||||
description:
|
description:
|
||||||
description == null
|
description == null
|
||||||
|
@ -206,7 +212,7 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
revert: (gain: DecimalSource) => {
|
invert: (gain: DecimalSource) => {
|
||||||
let result = gain;
|
let result = gain;
|
||||||
if (supportLowNumbers) {
|
if (supportLowNumbers) {
|
||||||
result = Decimal.add(result, 1);
|
result = Decimal.add(result, 1);
|
||||||
|
@ -217,6 +223,10 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
getFormula: (gain: FormulaSource) =>
|
||||||
|
supportLowNumbers
|
||||||
|
? Formula.add(gain, 1).pow(processedExponent).sub(1)
|
||||||
|
: Formula.pow(gain, processedExponent),
|
||||||
enabled: processedEnabled,
|
enabled: processedEnabled,
|
||||||
description:
|
description:
|
||||||
description == null
|
description == null
|
||||||
|
@ -259,9 +269,9 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
|
||||||
*/
|
*/
|
||||||
export function createSequentialModifier<
|
export function createSequentialModifier<
|
||||||
T extends Modifier[],
|
T extends Modifier[],
|
||||||
S = T extends WithRequired<Modifier, "revert">[]
|
S = T extends WithRequired<Modifier, "invert">[]
|
||||||
? WithRequired<Modifier, "description" | "revert">
|
? WithRequired<Modifier, "description" | "invert">
|
||||||
: Omit<WithRequired<Modifier, "description">, "revert">
|
: Omit<WithRequired<Modifier, "description">, "invert">
|
||||||
>(modifiersFunc: () => T): S {
|
>(modifiersFunc: () => T): S {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const modifiers = modifiersFunc();
|
const modifiers = modifiersFunc();
|
||||||
|
@ -271,15 +281,25 @@ export function createSequentialModifier<
|
||||||
modifiers
|
modifiers
|
||||||
.filter(m => unref(m.enabled) !== false)
|
.filter(m => unref(m.enabled) !== false)
|
||||||
.reduce((gain, modifier) => modifier.apply(gain), gain),
|
.reduce((gain, modifier) => modifier.apply(gain), gain),
|
||||||
revert: modifiers.every(m => m.revert != null)
|
invert: modifiers.every(m => m.invert != null)
|
||||||
? (gain: DecimalSource) =>
|
? (gain: DecimalSource) =>
|
||||||
modifiers
|
modifiers
|
||||||
.filter(m => unref(m.enabled) !== false)
|
.filter(m => unref(m.enabled) !== false)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
.reduceRight((gain, modifier) => modifier.revert!(gain), gain)
|
.reduceRight((gain, modifier) => modifier.invert!(gain), gain)
|
||||||
: undefined,
|
: undefined,
|
||||||
enabled: computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0),
|
getFormula: modifiers.every(m => m.getFormula != null)
|
||||||
description: jsx(() => (
|
? (gain: FormulaSource) =>
|
||||||
|
modifiers
|
||||||
|
.filter(m => unref(m.enabled) !== false)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
.reduce((acc, curr) => curr.getFormula!(acc), gain)
|
||||||
|
: undefined,
|
||||||
|
enabled: modifiers.some(m => m.enabled != null)
|
||||||
|
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
||||||
|
: undefined,
|
||||||
|
description: modifiers.some(m => m.description != null)
|
||||||
|
? jsx(() => (
|
||||||
<>
|
<>
|
||||||
{(
|
{(
|
||||||
modifiers
|
modifiers
|
||||||
|
@ -289,6 +309,7 @@ export function createSequentialModifier<
|
||||||
).map(renderJSX)}
|
).map(renderJSX)}
|
||||||
</>
|
</>
|
||||||
))
|
))
|
||||||
|
: undefined
|
||||||
};
|
};
|
||||||
}) as unknown as S;
|
}) as unknown as S;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ export const SaveDataPath = Symbol("SaveDataPath");
|
||||||
*/
|
*/
|
||||||
export const CheckNaN = Symbol("CheckNaN");
|
export const CheckNaN = Symbol("CheckNaN");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A symbol used to flag objects that should not be checked for persistent values.
|
||||||
|
*/
|
||||||
|
export const SkipPersistence = Symbol("SkipPersistence");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is a union of things that should be safely stringifiable without needing special processes or knowing what to load them in as.
|
* This is a union of things that should be safely stringifiable without needing special processes or knowing what to load them in as.
|
||||||
* - Decimals aren't allowed because we'd need to know to parse them back.
|
* - Decimals aren't allowed because we'd need to know to parse them back.
|
||||||
|
@ -196,12 +201,42 @@ export function isPersistent(value: unknown): value is Persistent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unwraps the non-persistent ref inside of persistent refs, to be passed to other features without duplicating values in the save data object.
|
* Unwraps the non-persistent ref inside of persistent refs, to be passed to other features without duplicating values in the save data object.
|
||||||
* @param persistent The persistent ref to unwrap
|
* @param persistent The persistent ref to unwrap, or an object to ignore all persistent refs within
|
||||||
*/
|
*/
|
||||||
export function noPersist<T extends Persistent<S>, S extends State>(
|
export function noPersist<T extends Persistent<S>, S extends State>(
|
||||||
persistent: T
|
persistent: T
|
||||||
): T[typeof NonPersistent] {
|
): T[typeof NonPersistent];
|
||||||
return persistent[NonPersistent];
|
export function noPersist<T extends object>(persistent: T): T;
|
||||||
|
export function noPersist<T extends Persistent<S>, S extends State>(persistent: T | object) {
|
||||||
|
// Check for proxy state so if it's a lazy proxy we don't evaluate it's function
|
||||||
|
// Lazy proxies are not persistent refs themselves, so we know we want to wrap them
|
||||||
|
return !(ProxyState in persistent) && NonPersistent in persistent
|
||||||
|
? persistent[NonPersistent]
|
||||||
|
: new Proxy(persistent, {
|
||||||
|
get(target, p) {
|
||||||
|
if (p === PersistentState) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
if (p === SkipPersistence) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return target[p as keyof typeof target];
|
||||||
|
},
|
||||||
|
set(target, key, value) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(target as Record<PropertyKey, any>)[key] = value;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
has(target, key) {
|
||||||
|
if (key === PersistentState) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (key == SkipPersistence) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return Reflect.has(target, key);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,6 +261,9 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
Object.keys(obj).forEach(key => {
|
Object.keys(obj).forEach(key => {
|
||||||
let value = obj[key];
|
let value = obj[key];
|
||||||
if (value != null && typeof value === "object") {
|
if (value != null && typeof value === "object") {
|
||||||
|
if ((value as Record<PropertyKey, unknown>)[SkipPersistence] === true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (ProxyState in value) {
|
if (ProxyState in value) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
value = (value as any)[ProxyState] as object;
|
value = (value as any)[ProxyState] as object;
|
||||||
|
|
14089
tests/game/__snapshots__/modifiers.test.ts.snap
Normal file
14089
tests/game/__snapshots__/modifiers.test.ts.snap
Normal file
File diff suppressed because it is too large
Load diff
411
tests/game/modifiers.test.ts
Normal file
411
tests/game/modifiers.test.ts
Normal file
|
@ -0,0 +1,411 @@
|
||||||
|
import { CoercableComponent, JSXFunction } from "features/feature";
|
||||||
|
import Formula, { printFormula } from "game/formulas/formulas";
|
||||||
|
import {
|
||||||
|
createAdditiveModifier,
|
||||||
|
createExponentialModifier,
|
||||||
|
createModifierSection,
|
||||||
|
createMultiplicativeModifier,
|
||||||
|
createSequentialModifier,
|
||||||
|
Modifier
|
||||||
|
} from "game/modifiers";
|
||||||
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
|
import { WithRequired } from "util/common";
|
||||||
|
import { Computable } from "util/computed";
|
||||||
|
import { beforeAll, describe, expect, test } from "vitest";
|
||||||
|
import { Ref, ref, unref } from "vue";
|
||||||
|
import "../utils";
|
||||||
|
|
||||||
|
export type ModifierConstructorOptions = {
|
||||||
|
[S in "addend" | "multiplier" | "exponent"]: Computable<DecimalSource>;
|
||||||
|
} & {
|
||||||
|
description?: Computable<CoercableComponent>;
|
||||||
|
enabled?: Computable<boolean>;
|
||||||
|
smallerIsBetter?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function testModifiers<
|
||||||
|
T extends "addend" | "multiplier" | "exponent",
|
||||||
|
S extends ModifierConstructorOptions
|
||||||
|
>(
|
||||||
|
modifierConstructor: (optionsFunc: () => S) => WithRequired<Modifier, "invert" | "getFormula">,
|
||||||
|
property: T,
|
||||||
|
operation: (lhs: DecimalSource, rhs: DecimalSource) => DecimalSource
|
||||||
|
) {
|
||||||
|
// Util because adding [property] messes up typing
|
||||||
|
function createModifier(
|
||||||
|
value: Computable<DecimalSource>,
|
||||||
|
options: Partial<ModifierConstructorOptions> = {}
|
||||||
|
): WithRequired<Modifier, "invert" | "getFormula"> {
|
||||||
|
options[property] = value;
|
||||||
|
return modifierConstructor(() => options as S);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("operations", () => {
|
||||||
|
let modifier: WithRequired<Modifier, "invert" | "getFormula">;
|
||||||
|
beforeAll(() => {
|
||||||
|
modifier = createModifier(ref(5));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Applies correctly", () =>
|
||||||
|
expect(modifier.apply(10)).compare_tolerance(operation(10, 5)));
|
||||||
|
test("Inverts correctly", () =>
|
||||||
|
expect(modifier.invert(operation(10, 5))).compare_tolerance(10));
|
||||||
|
test("getFormula returns the right formula", () => {
|
||||||
|
const value = ref(10);
|
||||||
|
expect(printFormula(modifier.getFormula(Formula.variable(value)))).toBe(
|
||||||
|
`${operation.name}(x, 5.00)`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applies description correctly", () => {
|
||||||
|
test("without description", () => expect(createModifier(0).description).toBeUndefined());
|
||||||
|
test("with description", () => {
|
||||||
|
const desc = createModifier(0, { description: "test" }).description;
|
||||||
|
expect(desc).not.toBeUndefined();
|
||||||
|
expect((desc as JSXFunction)()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applies enabled correctly", () => {
|
||||||
|
test("without enabled", () => expect(createModifier(0).enabled).toBeUndefined());
|
||||||
|
test("with enabled", () => {
|
||||||
|
const enabled = ref(false);
|
||||||
|
const modifier = createModifier(5, { enabled });
|
||||||
|
expect(modifier.enabled).toBe(enabled);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applies smallerIsBetter correctly", () => {
|
||||||
|
describe("without smallerIsBetter false", () => {
|
||||||
|
test("negative value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(-5, { description: "test", smallerIsBetter: false })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("zero value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(0, { description: "test", smallerIsBetter: false })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("positive value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(5, { description: "test", smallerIsBetter: false })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
});
|
||||||
|
describe("with smallerIsBetter true", () => {
|
||||||
|
test("negative value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(-5, { description: "test", smallerIsBetter: true })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("zero value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(0, { description: "test", smallerIsBetter: true })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("positive value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(5, { description: "test", smallerIsBetter: true })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Additive Modifiers", () => testModifiers(createAdditiveModifier, "addend", Decimal.add));
|
||||||
|
describe("Multiplicative Modifiers", () =>
|
||||||
|
testModifiers(createMultiplicativeModifier, "multiplier", Decimal.mul));
|
||||||
|
describe("Exponential Modifiers", () =>
|
||||||
|
testModifiers(createExponentialModifier, "exponent", Decimal.pow));
|
||||||
|
|
||||||
|
describe("Sequential Modifiers", () => {
|
||||||
|
function createModifier(
|
||||||
|
value: Computable<DecimalSource>,
|
||||||
|
options: Partial<ModifierConstructorOptions> = {}
|
||||||
|
): WithRequired<Modifier, "invert" | "getFormula"> {
|
||||||
|
return createSequentialModifier(() => [
|
||||||
|
createAdditiveModifier(() => ({ ...options, addend: value })),
|
||||||
|
createMultiplicativeModifier(() => ({ ...options, multiplier: value })),
|
||||||
|
createExponentialModifier(() => ({ ...options, exponent: value }))
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("operations", () => {
|
||||||
|
let modifier: WithRequired<Modifier, "invert" | "getFormula">;
|
||||||
|
beforeAll(() => {
|
||||||
|
modifier = createModifier(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Applies correctly", () =>
|
||||||
|
expect(modifier.apply(10)).compare_tolerance(Decimal.add(10, 5).times(5).pow(5)));
|
||||||
|
test("Inverts correctly", () =>
|
||||||
|
expect(modifier.invert(Decimal.add(10, 5).times(5).pow(5))).compare_tolerance(10));
|
||||||
|
test("getFormula returns the right formula", () => {
|
||||||
|
const value = ref(10);
|
||||||
|
expect(printFormula(modifier.getFormula(Formula.variable(value)))).toBe(
|
||||||
|
`pow(mul(add(x, 5.00), 5.00), 5.00)`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applies description correctly", () => {
|
||||||
|
test("without description", () => expect(createModifier(0).description).toBeUndefined());
|
||||||
|
test("with description", () => {
|
||||||
|
const desc = createModifier(0, { description: "test" }).description;
|
||||||
|
expect(desc).not.toBeUndefined();
|
||||||
|
expect((desc as JSXFunction)()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test("with both", () => {
|
||||||
|
const desc = createSequentialModifier(() => [
|
||||||
|
createAdditiveModifier(() => ({ addend: 0 })),
|
||||||
|
createMultiplicativeModifier(() => ({ multiplier: 0, description: "test" }))
|
||||||
|
]).description;
|
||||||
|
expect(desc).not.toBeUndefined();
|
||||||
|
expect((desc as JSXFunction)()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applies enabled correctly", () => {
|
||||||
|
test("without enabled", () => expect(createModifier(0).enabled).toBeUndefined());
|
||||||
|
test("with enabled", () => {
|
||||||
|
const enabled = ref(false);
|
||||||
|
const modifier = createModifier(5, { enabled });
|
||||||
|
expect(modifier.enabled).not.toBeUndefined();
|
||||||
|
expect(unref(modifier.enabled)).toBe(false);
|
||||||
|
enabled.value = true;
|
||||||
|
expect(unref(modifier.enabled)).toBe(true);
|
||||||
|
});
|
||||||
|
test("with both", () => {
|
||||||
|
const enabled = ref(false);
|
||||||
|
const modifier = createSequentialModifier(() => [
|
||||||
|
createAdditiveModifier(() => ({ addend: 0 })),
|
||||||
|
createMultiplicativeModifier(() => ({ multiplier: 0, enabled }))
|
||||||
|
]);
|
||||||
|
expect(modifier.enabled).not.toBeUndefined();
|
||||||
|
// So long as one is true or undefined, enable should be true
|
||||||
|
expect(unref(modifier.enabled)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("applies smallerIsBetter correctly", () => {
|
||||||
|
describe("without smallerIsBetter false", () => {
|
||||||
|
test("negative value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(-5, { description: "test", smallerIsBetter: false })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("zero value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(0, { description: "test", smallerIsBetter: false })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("positive value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(5, { description: "test", smallerIsBetter: false })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
});
|
||||||
|
describe("with smallerIsBetter true", () => {
|
||||||
|
test("negative value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(-5, { description: "test", smallerIsBetter: true })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("zero value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(0, { description: "test", smallerIsBetter: true })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("positive value", () =>
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
createModifier(5, { description: "test", smallerIsBetter: true })
|
||||||
|
.description as JSXFunction
|
||||||
|
)()
|
||||||
|
).toMatchSnapshot());
|
||||||
|
});
|
||||||
|
describe("with both", () => {
|
||||||
|
let value: Ref<DecimalSource>;
|
||||||
|
let modifier: Modifier;
|
||||||
|
beforeAll(() => {
|
||||||
|
value = ref(0);
|
||||||
|
modifier = createSequentialModifier(() => [
|
||||||
|
createAdditiveModifier(() => ({
|
||||||
|
addend: value,
|
||||||
|
description: "test",
|
||||||
|
smallerIsBetter: true
|
||||||
|
})),
|
||||||
|
createAdditiveModifier(() => ({
|
||||||
|
addend: value,
|
||||||
|
description: "test",
|
||||||
|
smallerIsBetter: false
|
||||||
|
}))
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test("negative value", () => {
|
||||||
|
value.value = -5;
|
||||||
|
expect((modifier.description as JSXFunction)()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test("zero value", () => {
|
||||||
|
value.value = 0;
|
||||||
|
expect((modifier.description as JSXFunction)()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test("positive value", () => {
|
||||||
|
value.value = 5;
|
||||||
|
expect((modifier.description as JSXFunction)()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Create modifier sections", () => {
|
||||||
|
test("No optional values", () =>
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({ addend: 5, description: "Test Desc" }))
|
||||||
|
})
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("With subtitle", () =>
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
subtitle: "Subtitle",
|
||||||
|
modifier: createAdditiveModifier(() => ({ addend: 5, description: "Test Desc" }))
|
||||||
|
})
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("With base", () =>
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({ addend: 5, description: "Test Desc" })),
|
||||||
|
base: 10
|
||||||
|
})
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("With unit", () =>
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({ addend: 5, description: "Test Desc" })),
|
||||||
|
unit: "/s"
|
||||||
|
})
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("With base", () =>
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({ addend: 5, description: "Test Desc" })),
|
||||||
|
baseText: "Based on"
|
||||||
|
})
|
||||||
|
).toMatchSnapshot());
|
||||||
|
test("With baseText", () =>
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({ addend: 5, description: "Test Desc" })),
|
||||||
|
baseText: "Based on"
|
||||||
|
})
|
||||||
|
).toMatchSnapshot());
|
||||||
|
describe("With smallerIsBetter", () => {
|
||||||
|
test("smallerIsBetter = false", () => {
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({
|
||||||
|
addend: -5,
|
||||||
|
description: "Test Desc"
|
||||||
|
})),
|
||||||
|
smallerIsBetter: false
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({
|
||||||
|
addend: 0,
|
||||||
|
description: "Test Desc"
|
||||||
|
})),
|
||||||
|
smallerIsBetter: false
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({
|
||||||
|
addend: 5,
|
||||||
|
description: "Test Desc"
|
||||||
|
})),
|
||||||
|
smallerIsBetter: false
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
test("smallerIsBetter = true", () => {
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({
|
||||||
|
addend: -5,
|
||||||
|
description: "Test Desc"
|
||||||
|
})),
|
||||||
|
smallerIsBetter: true
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({
|
||||||
|
addend: 0,
|
||||||
|
description: "Test Desc"
|
||||||
|
})),
|
||||||
|
smallerIsBetter: true
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
modifier: createAdditiveModifier(() => ({
|
||||||
|
addend: 5,
|
||||||
|
description: "Test Desc"
|
||||||
|
})),
|
||||||
|
smallerIsBetter: true
|
||||||
|
})
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test("With everything", () =>
|
||||||
|
expect(
|
||||||
|
createModifierSection({
|
||||||
|
title: "Test",
|
||||||
|
subtitle: "Subtitle",
|
||||||
|
modifier: createAdditiveModifier(() => ({ addend: 5, description: "Test Desc" })),
|
||||||
|
base: 10,
|
||||||
|
unit: "/s",
|
||||||
|
baseText: "Based on",
|
||||||
|
smallerIsBetter: true
|
||||||
|
})
|
||||||
|
).toMatchSnapshot());
|
||||||
|
});
|
Loading…
Reference in a new issue