Why does LokiSFSAdapter work on Linux, but not on Windows? - javascript

TL;DR A piece of Javascript code works flawlessly on Linux whilst behaving inconsistently on Windows.
I am coding an Electron app, using Vue.js for frontend, Vuex for data management and LokiJS for persistence storage (with its File System adapter at the background). I develop this application on Linux, but from time to time I have to switch to Windows to create a Windows build for the client. The Linux build always works flawlessly, the Windows one misbehaves. I assumed it was a LokiJS issue, however, upon the isolation of LokiJS-specific code, it worked properly even on Windows.
Here is simplified store.js file, which contains all relevant Vuex and LokiJS-related code in my application.
import loki from 'lokijs'
import LokiSFSAdapter from 'lokijs/src/loki-fs-structured-adapter'
import MainState from '../index' // a Vuex.Store object
const state = {
ads: [],
profiles: []
}
var sfsAdapter = new LokiSFSAdapter('loki')
var db = new loki('database.json', {
autoupdate: true,
autoload: true,
autoloadCallback: setupHandler,
adapter: sfsAdapter
})
function setupCollection (collectionName) {
var collection = db.getCollection(collectionName)
if (collection === null) {
collection = db.addCollection(collectionName)
}
}
function setupHandler () {
setupCollection('ads')
setupCollection('profiles')
MainState.commit('updateAds')
MainState.commit('updateProfiles')
}
window.onbeforeunload = function () {
db.saveDatabase()
db.close()
}
const mutations = {
updateAds (state) {
state.ads = db.getCollection('ads').data.slice()
},
updateProfiles (state) {
state.profiles = db.getCollection('profiles').data.slice()
}
}
const actions = {
async addProfile (context) {
db.getCollection('profiles').insert({ /* default data */ })
db.saveDatabase()
context.commit('updateProfiles')
},
async updateProfile (context, obj) {
db.getCollection('profiles').update(obj)
db.saveDatabase()
context.commit('updateProfiles')
},
async deleteProfile (context, id) {
db.getCollection('profiles').removeWhere({'$loki': {'$eq': id}})
db.saveDatabase()
context.commit('updateProfiles')
},
async addAd (context) {
db.getCollection('ads').insert({ /* default data */ })
db.saveDatabase()
context.commit('updateAds')
},
async deleteAd (context, id) {
db.getCollection('ads').removeWhere({'$loki': {'$eq': id}})
db.saveDatabase()
context.commit('updateAds')
}
}
Behaviour on Linux
it calls setupHandler every time the application starts,
it correctly saves data to database.json and the respective collections to database.json.0 and database.json.1 for ads and profiles
when addAd() is called, it can access all the data properly by calling db.getCollection('ads'), and then insert() on it.
Behaviour on Windows
only calls setupHandler if database.json doesn't exist. It correctly creates database.json if it doesn't exist, though.
creates only one file - database.json.0, but doesn't save any data there, it's just an empty file. It doesn't even create database.json.1 for the second collection.
obviously, since no data is actually saved, db.getCollection('ads') and returns null, which results into TypeError: Cannot read property 'insert' of null when calling addAd() on the successive application runs.
if this run database.json was created, the application behaves normally, insert() seems to work, however, no data is saved on exit and the successive runs result in the behaviour in the point above.
Question
Is this a bug somewhere deep in LokiJS/Vuex, or is it just me misusing their API?

Related

Javascript intellisense and ctrl+click not working while using fixtures

I am performing playwright test automation using fixtures and have files as below. While running its working as expected so i know its not a playwright issue. My question is when i ctrl+click on loadAndLogin under test.step in TC_123.js with VS code, i am expecting it to navigate to the loadAndLogin method in LoadAndLogin.js. But this is not happening and how do i fix this?
// basePage.js
const base = require('#playwright/test');
const { LoadAndLogin } = require('../utilities/LoadAndLogin');
exports.test = base.test.extend({
loadAndLogin: async ({ page }, use) => {
await use(new LoadAndLogin(page));
},
});
exports.expect = base.expect;
// LoadAndLogin.js
const { test, expect } = require('#playwright/test');
const { LoginPage } = require('../pages/LoginPage');
exports.LoadAndLogin = class LoadAndLogin {
constructor(page) {
this.page = page;
this.loginPage = new LoginPage(page);
}
async loadAndLogin() {
// code to Login to the application
}
}
// TC_123.js
const { test } = require('../../fixtures/basePage');
test('TC_123', async function ({page, loadAndLogin}) {
await test.step('01_Load application and login', async function () {
await loadAndLogin.loadAndLogin();
});
});
I tried checking with playwright team in github and below was the response
https://github.com/microsoft/playwright/issues/20218
You need to either add // #ts-check and javascript type annotations and use type script to make the navigation work for fixtures. VSCode fails to infer all the types looking at the javascript code alone. Closing this issue as it is not a playwright defect. Also feel free to open a request for VS Code team.
i tried using //#ts-check and even created jsconfig.json but still i am not able to understand why it is not working

Commit mutation from action in another store via root? Vue2 Nuxt Vuex

This has been beating me up.
I have a vuex store, inside is a folder called "RyansBag" im using to test things.
I have two other folder, Alerts and Inventory. So the folder structure for each of these goes
store> Ryansbag/Alert/...
in my Inventory index.js file, we run a function to add an item to an inventory system.
async addInventory_Catalog({commit}, payload){
try{
const response = await this.$axios.put('Inventory/AddFromCatalogDefault', null, {
params:{
originalUPC: payload.upc,
clientID: payload.clientId,
saleprice: payload.sellPrice,
cost: payload.sellPrice,
Condition: payload.condition.conditionName,
Serial: payload.serialNumber,
Notes: payload.notes,
HoldDays: payload.holdDays,
}
});
console.log(response.data.success)
commit('RyansBag/Alerts/showAlerts', 'You have added a product!', {root: true})
return response.data;
} catch (error) { alert(error); console.log(error); }
},
Here we just pass the item down, and when it's done - commit the changes to our alert which is in store > RyansBag/Alerts.
You can see I tried to call it:
commit('RyansBag/Alerts/showAlerts', 'You have added a product!', {root: true})
My understanding was to simply state the commit is coming from this store as the root state...? But Im not sure if im supposed to register the commit in /Alerts as a global item somehow. ( https://vuex.vuejs.org/guide/modules.html#accessing-global-assets-in-namespaced-modules )
EDIT:::
Edit: There was no commit request in the action. added to post. Now however I get warning to not mutate state outside of state handlers..
Below is the mutation it's requesting to reach inside the alerts.
export const mutations = {
showAlerts(state, message) {
let timeout = 0
if (state.status.showAlert) {
state.status.showAlert = false
timeout = 300
}
setTimeout(() => {
state.status.showAlert = true
state.status.message = message
}, timeout)
},
hideAlerts(state) {
state.status.showAlert = false
},
}
The fix was a) making sure I called commit in the action parameters. and b) not using setTimeout in the mutation, but instead setting a mutation to hide, and using the actions to setTime.

Testing Cross Browser Extension With Jest, How To Mock Chrome Storage API?

After putting off testing for a while now due to Cypress not allowing visiting chrome:// urls, I decided to finally understand how to unit/integration test my extension - TabMerger. This comes after the many times that I had to manually test the ever growing functionality and in some cases forgot to check a thing or two. Having automated testing will certainly speed up the process and help me be more at peace when adding new functionality.
To do this, I chose Jest since my extension was made with React (CRA). I also used React Testing Library (#testing-library/react) to render all React components for testing.
As I recently made TabMerger open source, the full testing script can be found here
Here is the test case that I want to focus on for this question:
import React from "react";
import { render, fireEvent } from "#testing-library/react";
import * as TabFunc from "../src/Tab/Tab_functions";
import Tab from "../src/Tab/Tab";
var init_groups = {
"group-0": {
color: "#d6ffe0",
created: "11/12/2020 # 22:13:24",
tabs: [
{
title:
"Stack Overflow - Where Developers Learn, Share, & Build Careersaaaaaaaaaaaaaaaaaaaaaa",
url: "https://stackoverflow.com/",
},
{
title: "lichess.org • Free Online Chess",
url: "https://lichess.org/",
},
{
title: "Chess.com - Play Chess Online - Free Games",
url: "https://www.chess.com/",
},
],
title: "Chess",
},
"group-1": {
color: "#c7eeff",
created: "11/12/2020 # 22:15:11",
tabs: [
{
title: "Twitch",
url: "https://www.twitch.tv/",
},
{
title: "reddit: the front page of the internet",
url: "https://www.reddit.com/",
},
],
title: "Social",
},
};
describe("removeTab", () => {
it("correctly adjusts groups and counts when a tab is removed", () => {
var tabs = init_groups["group-0"].tabs;
const { container } = render(<Tab init_tabs={tabs} />);
expect(container.getElementsByClassName("draggable").length).toEqual(3);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
fireEvent.click(container.querySelector(".close-tab"));
expect(removeTabSpy).toHaveBeenCalledTimes(1);
expect(container.getElementsByClassName("draggable").length).toEqual(2); // fails (does not remove the tab for some reason)
});
});
I mocked the Chrome API according to my needs, but feel that something is missing. To mock the Chrome API I followed this post (along with many others, even for other test runners like Jasmine): testing chrome.storage.local.set with jest.
Even though the Chrome storage API is mocked, I think the issue lies in this function which gets called upon initial render. That is, I think the chrome.storage.local.get is not actually being executed, but am not sure why.
// ./src/Tab/Tab_functions.js
/**
* Sets the initial tabs based on Chrome's local storage upon initial render.
* If Chrome's local storage is empty, this is set to an empty array.
* #param {function} setTabs For re-rendering the group's tabs
* #param {string} id Used to get the correct group tabs
*/
export function setInitTabs(setTabs, id) {
chrome.storage.local.get("groups", (local) => {
var groups = local.groups;
setTabs((groups && groups[id] && groups[id].tabs) || []);
});
}
The reason I think the mocked Chrome storage API is not working properly is because when I manually set it in my tests, the number of tabs does not increase from 0. Which forced me to pass a prop (props.init_tabs) to my Tab component for testing purposes (https://github.com/lbragile/TabMerger/blob/f78a2694786d11e8270454521f92e679d182b577/src/Tab/Tab.js#L33-L35) - something I want to avoid if possible via setting local storage.
Can someone point me in the right direction? I would like to avoid using libraries like jest-chrome since they abstract too much and make it harder for me to understand what is going on in my tests.
I think I have a solution for this now, so I will share with others.
I made proper mocks for my chrome storage API to use localStorage:
// __mocks__/chromeMock.js
...
storage: {
local: {
...,
get: function (key, cb) {
const item = JSON.parse(localStorage.getItem(key));
cb({ [key]: item });
},
...,
set: function (obj, cb) {
const key = Object.keys(obj)[0];
localStorage.setItem(key, JSON.stringify(obj[key]));
cb();
},
},
...
},
...
Also, to simulate the tab settings on initial render, I have a beforeEach hook which sets my localStorage using the above mock:
// __tests__/Tab.spec.js
var init_ls_entry, init_tabs, mockSet;
beforeEach(() => {
chrome.storage.local.set({ groups: init_groups }, () => {});
init_ls_entry = JSON.parse(localStorage.getItem("groups"));
init_tabs = init_ls_entry["group-0"].tabs;
mockSet = jest.fn(); // mock for setState hooks
});
AND most importantly, when I render(<Tab/>), I noticed that I wasn't supplying the id prop which caused nothing to render (in terms of tabs from localStorage), so now I have this:
// __tests__/Tab.spec.js
describe("removeTab", () => {
it("correctly adjusts storage when a tab is removed", async () => {
const { container } = render(
<Tab id="group-0" setTabTotal={mockSet} setGroups={mockSet} />
);
var removeTabSpy = jest.spyOn(TabFunc, "removeTab");
var chromeSetSpy = jest.spyOn(chrome.storage.local, "set");
fireEvent.click(container.querySelector(".close-tab"));
await waitFor(() => {
expect(chromeSetSpy).toHaveBeenCalled();
});
chrome.storage.local.get("groups", (local) => {
expect(init_tabs.length).toEqual(3);
expect(local.groups["group-0"].tabs.length).toEqual(2);
expect(removeTabSpy).toHaveBeenCalledTimes(1);
});
expect.assertions(4);
});
});
Which passes!!
Now on to drag and drop testing 😊

Immersive Reader onPreferencesChanged event not firing as preferences are changing

In this javascript code which calls the immersive reader SDK, the onExit fires as expected, but the onPreferencesChanged never fires. What am I missing here?
async function LaunchImmersiveReader(title) {
const data = {
title: title,
chunks: [{
content: title,
lang: 'en'
}]
};
const token = await getImmersiveReaderTokenAsync();
const subdomain = await getImmersiveReaderSubdomainAsync();
const options = {
onPreferencesChanged: onPreferencesChangedCallback,
onExit: exitCallback
};
ImmersiveReader.launchAsync(token, subdomain, data, options);
}
function exitCallback() {
// this fires as I click the immersive reader exit/back button
}
function onPreferencesChangedCallback(value) {
// this never fires as I change font, size, etc.
}
What version of the SDK are you using? The preferences callback is part of 1.1.0
I did notice that a lot of our sample projects and documentation is using 1.0.0 still, which might be leading to the confusion

Meteor session is undefined after page redirect

I am making a game that requires a lobby of players, but no accounts. Kind of like the game, Spyfall. I am using Meteor Sessions to know which player joined the lobby so that I can return the proper data for that specific player. I have a join.js component where the user enters in the lobby access code and the user's name. This component also redirects the user to the lobby. Join.js is at the route, /join, and the lobbies are at the route, /:lobby. Here is the join.js handleSubmit method which takes the user input and puts it in the players collection:
handleSubmit(event) {
event.preventDefault();
var party = Players.findOne({code: this.refs.code.value});
if(typeof party !== 'undefined') {
Meteor.call('players.insert', this.refs.code.value, this.refs.name.value);
var playerId = Players.findOne({"name": this.refs.name.value})._id;
Meteor.call('players.current', playerId);
location.href = "/" + this.refs.code.value;
} else {
document.getElementById("error").innerHTML = 'Please enter a valid party code';
}
I am using Sessions in the Meteor.methods in the players.js collection to get the current user.
import { Mongo } from 'meteor/mongo';
import { Session } from 'meteor/session';
Meteor.methods({
'players.insert': function(code, name) {
console.log('adding player: ', name , code);
Players.insert({code: code, name: name});
},
'players.updateAll': function(ids, characters, banners, countries, ancestors) {
for (var i = 0; i < characters.length; i++){
Players.update({_id: ids[i]}, {$set: {character: characters[i], banner: banners[i], country: countries[i], ancestor: ancestors[i]},});
}
},
'players.current': function(playerId) {
Session.set("currentPlayer", playerId);
console.log(Session.get("currentPlayer"));
},
'players.getCurrent': function() {
return Session.get("currentPlayer");
}
});
export const Players = new Mongo.Collection('players');
The console.log in the 'players.current' method returns the proper player id, but once the page redirects to /:lobby, the players.getCurrent returns undefined. I want players.getCurrent to return the same value that the console.log returns. How do I fix this issue? This is the function to get the current player id in the lobby.js:
getCurrentPlayerId() {
return Meteor.call('players.getCurrent');
}
Per the Meteor API, Meteor methods are meant to be the way you define server side behavior that you call from the client. They are really intended to be defined on the server.
Methods are remote functions that Meteor clients can invoke with Meteor.call.
A Meteor method defined on the client simply acts as a stub.
Calling methods on the client defines stub functions associated with server methods of the same name
Based on your code it looks like you are doing everything client side. In fact, session is part of the Meteor client API (can't use on the server).
Session provides a global object on the client that you can use to store an arbitrary set of key-value pairs.
Therefore, If I were you, I would just implement all this logic in some sort of util file that you can then import into the Templates where you need it. You are effectively doing the same thing, you just need to use regular functions instead of Meteor methods.
Here is an example util file (be sure to update the Players import based upon your project's file structure).
import { Players } from './players.js';
import { Session } from 'meteor/session';
export const players = {
insert: function(code, name) {
console.log('adding player: ', name , code);
return Players.insert({code: code, name: name});
},
updateAll: function(ids, characters, banners, countries, ancestors) {
for (var i = 0; i < characters.length; i++) {
Players.update({_id: ids[i]}, {$set: {character: characters[i], banner: banners[i], country: countries[i], ancestor: ancestors[i]},});
}
},
setCurrent: function(playerId) {
Session.set("currentPlayer", playerId);
console.log(Session.get("currentPlayer"));
},
getCurrent: function(unixTimestamp) {
return Session.get("currentPlayer");
},
};
Then, you can import this into whatever template you have that has defined the event handler you included in your question.
import { Template } from 'meteor/templating';
import { players } from './utils.js';
Template.template_name.events({
'click .class': handleSubmit (event, instance) {
event.preventDefault();
var party = Players.findOne({code: this.refs.code.value});
if (typeof party !== 'undefined') {
var playerId = players.insert(this.refs.code.value, this.refs.name.value);
players.setCurrent(playerId);
location.href = "/" + this.refs.code.value;
} else {
document.getElementById("error").innerHTML = 'Please enter a valid party code';
}
},
});
Of course you will need to modify the above code to use your correct template name and location of the utils file.
I think the issue is that you are using
location.href = "/" + this.refs.code.value;
instead of using
Router.go("/"+this.refs.code.value);
if using Iron Router. Doing this is as if you are refreshing the page. And here's a package to maintain Session variables across page refreshes.

Categories

Resources