running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

commit 8d42bd67ba4b6f6dcb5ef4586ff9c699c8ff4e58
parent 346ce417450319c09d36269aad7eb4f44975b084
Author: Asher Morgan <59518073+ashermorgan@users.noreply.github.com>
Date:   Sat, 31 May 2025 12:58:42 -0700

Automatically migrate workout calculator options

Diffstat:
Msrc/composables/useStorage.js | 12+++---------
Msrc/main.js | 14++++++++------
Asrc/utils/storage.js | 36++++++++++++++++++++++++++++++++++++
Atests/unit/utils/storage.spec.js | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 136 insertions(+), 15 deletions(-)

diff --git a/src/composables/useStorage.js b/src/composables/useStorage.js @@ -1,7 +1,6 @@ import { ref, onActivated, watchEffect } from 'vue'; -// The global localStorage prefix -const prefix = 'running-tools'; +import * as storage from '@/utils/storage'; /* * Create a reactive value that is synced with a localStorage item @@ -14,12 +13,7 @@ export default function useStorage(key, defaultValue) { // (Re)load value from localStorage function updateValue() { - let parsedValue; - try { - parsedValue = JSON.parse(localStorage.getItem(`${prefix}.${key}`)); - } catch { - parsedValue = null; - } + let parsedValue = storage.get(key); if (parsedValue !== null) value.value = parsedValue; } updateValue(); @@ -28,7 +22,7 @@ export default function useStorage(key, defaultValue) { // Save value to localStorage when modified watchEffect(() => { if (typeof localStorage !== 'undefined') { - localStorage.setItem(`${prefix}.${key}`, JSON.stringify(value.value)); + storage.set(key, value.value); } }) diff --git a/src/main.js b/src/main.js @@ -1,11 +1,13 @@ -import './assets/global.css'; - import { createApp } from 'vue'; -import App from './App.vue'; -import router from './router'; -const app = createApp(App); +import App from '@/App.vue'; +import router from '@/router'; +import * as storage from '@/utils/storage'; -app.use(router); +import '@/assets/global.css'; +storage.migrate(); + +const app = createApp(App); +app.use(router); app.mount('#app'); diff --git a/src/utils/storage.js b/src/utils/storage.js @@ -0,0 +1,36 @@ +// The global localStorage prefix +const prefix = 'running-tools'; + +/** + * Read an object from a localStorage item + * @param {String} key The localStorage item's key + * @returns {Object} The object + */ +export function get(key) { + try { + return JSON.parse(localStorage.getItem(`${prefix}.${key}`)); + } catch { + return null; + } +} + +/** + * Write an object to a localStorage item + * @param {String} key The localStorage item's key + * @param {Object} value The object to write + */ +export function set(key, value) { + localStorage.setItem(`${prefix}.${key}`, JSON.stringify(value)); +} + +/** + * Migrate outdated localStorage options + */ +export function migrate() { + // Add customTargetNames property to workout options (>1.4.1) + let workoutOptions = get('workout-calculator-options'); + if (workoutOptions !== null && workoutOptions.customTargetNames === undefined) { + workoutOptions.customTargetNames = false; + set('workout-calculator-options', workoutOptions); + } +} diff --git a/tests/unit/utils/storage.spec.js b/tests/unit/utils/storage.spec.js @@ -0,0 +1,89 @@ +import { beforeEach, describe, test, expect } from 'vitest'; +import * as storage from '@/utils/storage'; + +beforeEach(() => { + localStorage.clear(); +}) + +describe('get method', () => { + test('should correctly parse correct localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', '{"bar":123}'); + + // Assert result is correct + expect(storage.get('foo')).to.deep.equal({ bar: 123 }); + }); + + test('should return null for corrupt localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', 'invalid json'); + + // Assert result is correct + expect(storage.get('foo')).to.equal(null); + }); + + test('should return null for missing localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', '{"bar":123}'); + + // Assert result is correct + expect(storage.get('baz')).to.equal(null); + }); +}); + +describe('set method', () => { + test('should correctly set new localStorage item', async () => { + // Set localStorage item + storage.set('foo', { baz: 456 }); + + // Assert result is correct + expect(localStorage.getItem('running-tools.foo')).to.equal('{"baz":456}'); + }); + + test('should correctly override existing localStorage item', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.foo', '{"bar":123}'); + + // Set localStorage item + storage.set('foo', { baz: 456 }); + + // Assert result is correct + expect(localStorage.getItem('running-tools.foo')).to.equal('{"baz":456}'); + }); +}); + +describe('migrate method', () => { + test('should correctly migrate <=1.4.1 workout calculator options', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.workout-calculator-options', + '{"model":"PurdyPointsModel","riegelExponent":1.1}'); + + // Run migratinos + storage.migrate(); + + // Assert localStorage entries correctly migrated + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal( + '{"model":"PurdyPointsModel","riegelExponent":1.1,"customTargetNames":false}'); + }); + + test('should not modify >1.4.1 workout calculator options', async () => { + // Initialize localStorage + localStorage.setItem('running-tools.workout-calculator-options', + '{"customTargetNames":true,"model":"PurdyPointsModel","riegelExponent":1.1}'); + + // Run migratinos + storage.migrate(); + + // Assert localStorage entries not modified + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal( + '{"customTargetNames":true,"model":"PurdyPointsModel","riegelExponent":1.1}'); + }); + + test('should not modify missing workout calculator options', async () => { + // Run migratinos + storage.migrate(); + + // Assert localStorage entries not modified + expect(localStorage.getItem('running-tools.workout-calculator-options')).to.equal(null); + }); +});