Merge branch 'master' into fix-splitfreq-weights

* master:
  style: apply prettier fixes
  style: switch prettier to enforce single quotes
  style: enforce prettier rules in linter
  chore: silence erroneous warning for missing moment.js install
This commit is contained in:
Tom French 2022-08-23 15:57:50 +01:00
commit 8f3e9d4b37
53 changed files with 566 additions and 432 deletions

10
.eslintrc.js Normal file
View file

@ -0,0 +1,10 @@
module.exports = {
plugins: ["prettier"],
extends: ["react-app"],
rules: {
"prettier/prettier": "error",
"no-unused-vars": "off",
"no-loop-func": "off",
"no-restricted-globals": "off"
}
};

4
.prettierrc.json Normal file
View file

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "none"
}

View file

@ -44,18 +44,6 @@
"shelljs": "^0.8.2", "shelljs": "^0.8.2",
"source-map-support": "^0.5.21" "source-map-support": "^0.5.21"
}, },
"eslintConfig": {
"extends": "react-app",
"rules": {
"no-unused-vars": "off",
"no-loop-func": "off",
"no-restricted-globals": "off"
}
},
"prettier": {
"singleQuote": true,
"trailingComma": "none"
},
"resolutions": { "resolutions": {
"@babel/preset-env": "^7.15.1", "@babel/preset-env": "^7.15.1",
"@babel/core": "^7.15.1", "@babel/core": "^7.15.1",

View file

@ -518,6 +518,10 @@ module.exports = function(webpackEnv) {
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js: // You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// Pikaday throws a warning if Moment.js is not installed however it doesn't
// actually require it to be installed. As we don't use Moment.js ourselves
// then we can just silence this warning.
new webpack.IgnorePlugin(/moment$/, /pikaday$/),
!(isEnvDevelopment || process.env.PERF_BUILD) && !(isEnvDevelopment || process.env.PERF_BUILD) &&
new webpack.IgnorePlugin(/perf-deets\/frontend/), new webpack.IgnorePlugin(/perf-deets\/frontend/),
// Generate a service worker script that will precache, and keep up to date, // Generate a service worker script that will precache, and keep up to date,

View file

@ -9,14 +9,18 @@ function getFlex(flex) {
} }
function Box({ flex, children, direction, style }) { function Box({ flex, children, direction, style }) {
return <div return (
<div
style={{ style={{
...style, ...style,
flex: getFlex(flex), flex: getFlex(flex),
display: 'flex', display: 'flex',
flexDirection: direction || 'column' flexDirection: direction || 'column'
}} }}
>{children}</div>; >
{children}
</div>
);
} }
export default Box; export default Box;

View file

@ -206,7 +206,7 @@ class Debugger extends React.Component {
height: 10, height: 10,
backgroundColor: '#303030', backgroundColor: '#303030',
marginRight: 10, marginRight: 10,
borderRadius: 10, borderRadius: 10
}} }}
/> />
<Button onClick={this.toggleRecord} style={{ marginRight: 10 }}> <Button onClick={this.toggleRecord} style={{ marginRight: 10 }}>

View file

@ -99,7 +99,9 @@ class FatalError extends React.Component {
. .
</P> </P>
<P> <P>
<Button onClick={() => window.Actual.relaunch()}>{buttonText}</Button> <Button onClick={() => window.Actual.relaunch()}>
{buttonText}
</Button>
</P> </P>
<P isLast={true} style={{ fontSize: 11 }}> <P isLast={true} style={{ fontSize: 11 }}>
<Link <Link

View file

@ -277,7 +277,4 @@ function FinancesAppWithContext(props) {
); );
} }
export default connect( export default connect(null, actions)(FinancesAppWithContext);
null,
actions
)(FinancesAppWithContext);

View file

@ -642,7 +642,7 @@ const AccountHeader = React.memo(
let searchInput = useRef(null); let searchInput = useRef(null);
let splitsExpanded = useSplitsExpanded(); let splitsExpanded = useSplitsExpanded();
let canSync = syncEnabled && (account && account.account_id); let canSync = syncEnabled && account && account.account_id;
if (!account) { if (!account) {
// All accounts - check for any syncable account // All accounts - check for any syncable account
canSync = !!accounts.find(account => !!account.account_id); canSync = !!accounts.find(account => !!account.account_id);
@ -1701,9 +1701,9 @@ class AccountInternal extends React.PureComponent {
} }
showAccount={ showAccount={
!accountId || !accountId ||
(accountId === 'offbudget' || accountId === 'offbudget' ||
accountId === 'budgeted' || accountId === 'budgeted' ||
accountId === 'uncategorized') accountId === 'uncategorized'
} }
isAdding={this.state.isAdding} isAdding={this.state.isAdding}
isNew={this.isNew} isNew={this.isNew}

View file

@ -149,7 +149,11 @@ function ConfigureField({ field, op, value, dispatch, onApply }) {
['amount-outflow', 'Amount (outflow)'] ['amount-outflow', 'Amount (outflow)']
] ]
: field === 'date' : field === 'date'
? [['date', 'Date'], ['month', 'Month'], ['year', 'Year']] ? [
['date', 'Date'],
['month', 'Month'],
['year', 'Year']
]
: null : null
} }
value={subfield} value={subfield}

View file

@ -230,8 +230,7 @@ class ManagementApp extends React.Component {
} }
} }
export default connect( export default connect(state => {
state => {
let { modalStack } = state.modals; let { modalStack } = state.modals;
return { return {
@ -241,6 +240,4 @@ export default connect(
loadingText: state.app.loadingText, loadingText: state.app.loadingText,
currentModals: modalStack.map(modal => modal.name) currentModals: modalStack.map(modal => modal.name)
}; };
}, }, actions)(ManagementApp);
actions
)(ManagementApp);

View file

@ -69,10 +69,7 @@ export function ConfirmPasswordForm({ buttons, onSetPassword, onError }) {
</label> </label>
<View style={{ flex: 1 }} /> <View style={{ flex: 1 }} />
{buttons} {buttons}
<ButtonWithLoading <ButtonWithLoading primary loading={loading}>
primary
loading={loading}
>
OK OK
</ButtonWithLoading> </ButtonWithLoading>
</View> </View>

View file

@ -70,7 +70,4 @@ function WelcomeScreen({ modalProps, actions }) {
); );
} }
export default connect( export default connect(null, actions)(WelcomeScreen);
null,
actions
)(WelcomeScreen);

View file

@ -2,7 +2,7 @@ import React from 'react';
import { styles } from 'loot-design/src/style'; import { styles } from 'loot-design/src/style';
import { integerToCurrency } from 'loot-core/src/shared/util'; import { integerToCurrency } from 'loot-core/src/shared/util';
import { Block } from 'loot-design/src/components/common'; import { Block } from 'loot-design/src/components/common';
import { colors } from 'loot-design/src/style' import { colors } from 'loot-design/src/style';
function Change({ amount, style }) { function Change({ amount, style }) {
return ( return (

View file

@ -110,7 +110,10 @@ function CashFlowCard() {
const end = monthUtils.currentDay(); const end = monthUtils.currentDay();
const start = monthUtils.currentMonth() + '-01'; const start = monthUtils.currentMonth() + '-01';
const data = useReport('cash_flow_simple', useArgsMemo(simpleCashFlow)(start, end)); const data = useReport(
'cash_flow_simple',
useArgsMemo(simpleCashFlow)(start, end)
);
if (!data) { if (!data) {
return null; return null;
} }

View file

@ -15,9 +15,9 @@ function BudgetInitial({ accounts, navigationProps }) {
{!minimized && ( {!minimized && (
<React.Fragment> <React.Fragment>
<P> <P>
You should see all of your current accounts' balance available You should see all of your current accounts' balance available to
to budget. Click on the budgeted column for a category create a budget. Click on the budgeted column for a category create a budget.
budget. Keep doing this until your "To Budget" amount is zero. Keep doing this until your "To Budget" amount is zero.
</P> </P>
<P> <P>
Don't worry too much about your initial budget. Just guess. You'll Don't worry too much about your initial budget. Just guess. You'll

View file

@ -61,7 +61,6 @@ function BudgetNextMonth({ stepTwo, navigationProps }) {
); );
} }
export default connect( export default connect(null, dispatch => bindActionCreators(actions, dispatch))(
null, BudgetNextMonth
dispatch => bindActionCreators(actions, dispatch) );
)(BudgetNextMonth);

View file

@ -5,7 +5,7 @@ import { styles, colors } from 'loot-design/src/style';
import { Standalone, Title, useMinimized } from './common'; import { Standalone, Title, useMinimized } from './common';
function CategoryBalance({ targetRect, navigationProps }) { function CategoryBalance({ targetRect, navigationProps }) {
let [minimized, toggle] = useMinimized() let [minimized, toggle] = useMinimized();
return ( return (
<Standalone> <Standalone>

View file

@ -96,7 +96,6 @@ function Overspending({ navigationProps, stepTwo }) {
); );
} }
export default connect( export default connect(null, dispatch => bindActionCreators(actions, dispatch))(
null, Overspending
dispatch => bindActionCreators(actions, dispatch) );
)(Overspending);

View file

@ -33,8 +33,8 @@ function TransactionAdd({ targetRect, navigationProps }) {
</P> </P>
<P isLast={true}> <P isLast={true}>
Try <strong>clicking "Add New"</strong> to see how adding Try <strong>clicking "Add New"</strong> to see how adding transactions
transactions affects your budget. affects your budget.
</P> </P>
<Navigation {...navigationProps} showBack={false} /> <Navigation {...navigationProps} showBack={false} />

View file

@ -47,6 +47,7 @@
"damerau-levenshtein": "^1.0.4", "damerau-levenshtein": "^1.0.4",
"date-fns": "2.0.0-alpha.27", "date-fns": "2.0.0-alpha.27",
"eslint": "5.6.0", "eslint": "5.6.0",
"eslint-plugin-prettier": "^3.1.4",
"esm": "^3.0.82", "esm": "^3.0.82",
"fake-indexeddb": "^3.1.3", "fake-indexeddb": "^3.1.3",
"fast-check": "2.13.0", "fast-check": "2.13.0",
@ -60,6 +61,7 @@
"mockdate": "^3.0.5", "mockdate": "^3.0.5",
"murmurhash": "^0.0.2", "murmurhash": "^0.0.2",
"perf-deets": "^1.0.15", "perf-deets": "^1.0.15",
"prettier": "^1.19.1",
"sanitize-filename": "^1.6.1", "sanitize-filename": "^1.6.1",
"search-query-parser": "^1.3.0", "search-query-parser": "^1.3.0",
"snapshot-diff": "^0.2.2", "snapshot-diff": "^0.2.2",

View file

@ -5,5 +5,5 @@ export function debugCell(sheet, name) {
type: constants.DEBUG_CELL, type: constants.DEBUG_CELL,
sheet, sheet,
name name
} };
} }

View file

@ -439,15 +439,19 @@ async function fillOther(handlers, account, payees, groups) {
async function createBudget(accounts, payees, groups) { async function createBudget(accounts, payees, groups) {
let primaryAccount = accounts.find(a => (a.name = 'Bank of America')); let primaryAccount = accounts.find(a => (a.name = 'Bank of America'));
let earliestDate = (await db.first( let earliestDate = (
await db.first(
`SELECT * FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id `SELECT * FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id
WHERE a.offbudget = 0 AND t.is_child = 0 ORDER BY date ASC LIMIT 1` WHERE a.offbudget = 0 AND t.is_child = 0 ORDER BY date ASC LIMIT 1`
)).date; )
let earliestPrimaryDate = (await db.first( ).date;
let earliestPrimaryDate = (
await db.first(
`SELECT * FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id `SELECT * FROM v_transactions t LEFT JOIN accounts a ON t.account = a.id
WHERE a.id = ? AND a.offbudget = 0 AND t.is_child = 0 ORDER BY date ASC LIMIT 1`, WHERE a.id = ? AND a.offbudget = 0 AND t.is_child = 0 ORDER BY date ASC LIMIT 1`,
[primaryAccount.id] [primaryAccount.id]
)).date; )
).date;
let start = monthUtils.monthFromDate(db.fromDateRepr(earliestDate)); let start = monthUtils.monthFromDate(db.fromDateRepr(earliestDate));
let end = monthUtils.currentMonth(); let end = monthUtils.currentMonth();

View file

@ -172,7 +172,10 @@ module.exports.listen = function listen(name, cb) {
return () => { return () => {
let arr = listeners.get(name); let arr = listeners.get(name);
listeners.set(name, arr.filter(cb_ => cb_ !== cb)); listeners.set(
name,
arr.filter(cb_ => cb_ !== cb)
);
}; };
}; };

View file

@ -82,7 +82,10 @@ function listen(name, cb) {
return () => { return () => {
let arr = listeners.get(name); let arr = listeners.get(name);
listeners.set(name, arr.filter(cb_ => cb_ !== cb)); listeners.set(
name,
arr.filter(cb_ => cb_ !== cb)
);
}; };
} }

View file

@ -56,6 +56,9 @@ module.exports.listen = function listen(name, cb) {
return () => { return () => {
let arr = listeners.get(name); let arr = listeners.get(name);
listeners.set(name, arr.filter(cb_ => cb_ !== cb)); listeners.set(
name,
arr.filter(cb_ => cb_ !== cb)
);
}; };
}; };

View file

@ -117,7 +117,10 @@ module.exports.listen = function listen(name, cb) {
return () => { return () => {
let arr = listeners.get(name); let arr = listeners.get(name);
if (arr) { if (arr) {
listeners.set(name, arr.filter(cb_ => cb_ !== cb)); listeners.set(
name,
arr.filter(cb_ => cb_ !== cb)
);
} }
}; };
}; };

View file

@ -1,5 +1,3 @@
export function captureException(exc) { export function captureException(exc) {}
}
export function captureBreadcrumb(info) { export function captureBreadcrumb(info) {}
}

View file

@ -167,9 +167,9 @@ async function _removeFile(filepath) {
// Load files from the server that should exist by default // Load files from the server that should exist by default
async function populateDefaultFilesystem() { async function populateDefaultFilesystem() {
let index = await (await fetch( let index = await (
process.env.PUBLIC_URL + 'data-file-index.txt' await fetch(process.env.PUBLIC_URL + 'data-file-index.txt')
)).text(); ).text();
let files = index let files = index
.split('\n') .split('\n')
.map(name => name.trim()) .map(name => name.trim())

View file

@ -1,18 +1,18 @@
const conjunctions = [ const conjunctions = [
'for', 'for', //
'and', 'and',
'nor', 'nor',
'but', 'but',
'or', 'or',
'yet', 'yet',
'so' 'so'
] ];
const articles = [ const articles = [
'a', 'a', //
'an', 'an',
'the' 'the'
] ];
const prepositions = [ const prepositions = [
'aboard', 'aboard',
@ -84,10 +84,6 @@ const prepositions = [
'with', 'with',
'within', 'within',
'without' 'without'
] ];
module.exports = new Set([ module.exports = new Set([...conjunctions, ...articles, ...prepositions]);
...conjunctions,
...articles,
...prepositions
])

View file

@ -116,9 +116,9 @@ export async function batchUpdateTransactions({
await Promise.all(allAdded.map(t => transfer.onInsert(t))); await Promise.all(allAdded.map(t => transfer.onInsert(t)));
// Return any updates from here // Return any updates from here
resultUpdated = (await Promise.all( resultUpdated = (
allUpdated.map(t => transfer.onUpdate(t)) await Promise.all(allUpdated.map(t => transfer.onUpdate(t)))
)).filter(Boolean); ).filter(Boolean);
await Promise.all(allDeleted.map(t => transfer.onDelete(t))); await Promise.all(allDeleted.map(t => transfer.onDelete(t)));
}); });

View file

@ -6,24 +6,28 @@ async function getPayee(acct) {
async function getTransferredAccount(transaction) { async function getTransferredAccount(transaction) {
if (transaction.payee) { if (transaction.payee) {
let { transfer_acct, id } = await db.first( let {
'SELECT id, transfer_acct FROM v_payees WHERE id = ?', transfer_acct,
[transaction.payee] id
); } = await db.first('SELECT id, transfer_acct FROM v_payees WHERE id = ?', [
transaction.payee
]);
return transfer_acct; return transfer_acct;
} }
return null; return null;
} }
async function clearCategory(transaction, transferAcct) { async function clearCategory(transaction, transferAcct) {
const { offbudget: fromOffBudget } = await db.first( const {
'SELECT offbudget FROM accounts WHERE id = ?', offbudget: fromOffBudget
[transaction.account] } = await db.first('SELECT offbudget FROM accounts WHERE id = ?', [
); transaction.account
const { offbudget: toOffBudget } = await db.first( ]);
'SELECT offbudget FROM accounts WHERE id = ?', const {
[transferAcct] offbudget: toOffBudget
); } = await db.first('SELECT offbudget FROM accounts WHERE id = ?', [
transferAcct
]);
// We should clear the category to make sure it's not being // We should clear the category to make sure it's not being
// accounted for in the budget, unless it should be in the case of // accounted for in the budget, unless it should be in the case of
@ -36,10 +40,11 @@ async function clearCategory(transaction, transferAcct) {
} }
export async function addTransfer(transaction, transferredAccount) { export async function addTransfer(transaction, transferredAccount) {
let { id: fromPayee } = await db.first( let {
'SELECT id FROM payees WHERE transfer_acct = ?', id: fromPayee
[transaction.account] } = await db.first('SELECT id FROM payees WHERE transfer_acct = ?', [
); transaction.account
]);
// We need to enforce certain constraints with child transaction transfers // We need to enforce certain constraints with child transaction transfers
if (transaction.parent_id) { if (transaction.parent_id) {

View file

@ -81,28 +81,34 @@ describe('runQuery', () => {
expect(data[0].date).toBe('2020-01-04'); expect(data[0].date).toBe('2020-01-04');
// date-month // date-month
data = (await runQuery( data = (
await runQuery(
query('transactions') query('transactions')
.select({ month: { $month: '$date' } }) .select({ month: { $month: '$date' } })
.serialize() .serialize()
)).data; )
).data;
expect(data[0].month).toBe('2020-01'); expect(data[0].month).toBe('2020-01');
// date-year // date-year
data = (await runQuery( data = (
await runQuery(
query('transactions') query('transactions')
.select({ year: { $year: '$date' } }) .select({ year: { $year: '$date' } })
.serialize() .serialize()
)).data; )
).data;
expect(data[0].year).toBe('2020'); expect(data[0].year).toBe('2020');
// boolean // boolean
data = (await runQuery( data = (
await runQuery(
query('transactions') query('transactions')
.select(['is_child', 'is_parent']) .select(['is_child', 'is_parent'])
.raw() .raw()
.serialize() .serialize()
)).data; )
).data;
expect(data[0].is_child).toBe(false); expect(data[0].is_child).toBe(false);
expect(data[0].is_parent).toBe(true); expect(data[0].is_parent).toBe(true);
expect(data[1].is_child).toBe(true); expect(data[1].is_child).toBe(true);
@ -128,31 +134,37 @@ describe('runQuery', () => {
); );
expect(data[0].id).toBe(transId); expect(data[0].id).toBe(transId);
data = (await runQuery( data = (
await runQuery(
query('transactions') query('transactions')
.filter({ date: { $transform: '$month', $eq: { $month: ':month' } } }) .filter({ date: { $transform: '$month', $eq: { $month: ':month' } } })
.select('date') .select('date')
.serialize(), .serialize(),
{ params: { month: '2020-01-02' } } { params: { month: '2020-01-02' } }
)).data; )
).data;
expect(data[0].id).toBe(transId); expect(data[0].id).toBe(transId);
data = (await runQuery( data = (
await runQuery(
query('transactions') query('transactions')
.filter({ date: { $transform: '$year', $eq: { $year: ':month' } } }) .filter({ date: { $transform: '$year', $eq: { $year: ':month' } } })
.select('date') .select('date')
.serialize(), .serialize(),
{ params: { month: '2020-01-02' } } { params: { month: '2020-01-02' } }
)).data; )
).data;
expect(data[0].id).toBe(transId); expect(data[0].id).toBe(transId);
data = (await runQuery( data = (
await runQuery(
query('transactions') query('transactions')
.filter({ cleared: ':cleared' }) .filter({ cleared: ':cleared' })
.select('date') .select('date')
.serialize(), .serialize(),
{ params: { cleared: true } } { params: { cleared: true } }
)).data; )
).data;
expect(data[0].id).toBe(transId); expect(data[0].id).toBe(transId);
}); });

View file

@ -142,7 +142,10 @@ async function execTransactionsGrouped(
rows = await db.all(rowSql, params); rows = await db.all(rowSql, params);
matched = new Set( matched = new Set(
[].concat.apply([], rows.map(row => row.matched.split(','))) [].concat.apply(
[],
rows.map(row => row.matched.split(','))
)
); );
} }

View file

@ -10,7 +10,7 @@ describe('schema', () => {
test('never returns transactions without a date', async () => { test('never returns transactions without a date', async () => {
expect((await db.all('SELECT * FROM transactions')).length).toBe(0); expect((await db.all('SELECT * FROM transactions')).length).toBe(0);
expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0); expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0);
await db.runQuery('INSERT INTO transactions (acct) VALUES (?)', ["foo"]); await db.runQuery('INSERT INTO transactions (acct) VALUES (?)', ['foo']);
expect((await db.all('SELECT * FROM transactions')).length).toBe(1); expect((await db.all('SELECT * FROM transactions')).length).toBe(1);
expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0); expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0);
}); });
@ -28,7 +28,7 @@ describe('schema', () => {
expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0); expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0);
await db.runQuery( await db.runQuery(
'INSERT INTO transactions (date, acct, isChild) VALUES (?, ?, ?)', 'INSERT INTO transactions (date, acct, isChild) VALUES (?, ?, ?)',
[20200101, "foo", 1] [20200101, 'foo', 1]
); );
expect((await db.all('SELECT * FROM transactions')).length).toBe(1); expect((await db.all('SELECT * FROM transactions')).length).toBe(1);
expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0); expect((await db.all('SELECT * FROM v_transactions')).length).toBe(0);

View file

@ -3,7 +3,6 @@ import { mutator } from '../mutators';
import { undoable } from '../undo'; import { undoable } from '../undo';
import * as actions from './actions'; import * as actions from './actions';
let app = createApp(); let app = createApp();
app.method('budget/budget-amount', mutator(undoable(actions.setBudget))); app.method('budget/budget-amount', mutator(undoable(actions.setBudget)));

View file

@ -19,4 +19,3 @@ export function unflatten2(arr) {
} }
return res; return res;
} }

View file

@ -1,4 +1,12 @@
import * as merkle from "./merkle"; import * as merkle from './merkle';
export { merkle }; export { merkle };
export { getClock, setClock, makeClock, makeClientId, serializeClock, deserializeClock, Timestamp } from "./timestamp" export {
getClock,
setClock,
makeClock,
makeClientId,
serializeClock,
deserializeClock,
Timestamp
} from './timestamp';

View file

@ -30,7 +30,16 @@ import * as bankSync from './accounts/sync';
import * as link from './accounts/link'; import * as link from './accounts/link';
import { uniqueFileName, idFromFileName } from './util/budget-name'; import { uniqueFileName, idFromFileName } from './util/budget-name';
import { mutator, runHandler } from './mutators'; import { mutator, runHandler } from './mutators';
import { getClock, setClock, makeClock, makeClientId, serializeClock, deserializeClock, Timestamp, merkle } from './crdt'; import {
getClock,
setClock,
makeClock,
makeClientId,
serializeClock,
deserializeClock,
Timestamp,
merkle
} from './crdt';
import { import {
initialFullSync, initialFullSync,
fullSync, fullSync,
@ -414,7 +423,10 @@ handlers['category-group-delete'] = mutator(async function({ id, transferId }) {
return batchMessages(async () => { return batchMessages(async () => {
if (transferId) { if (transferId) {
await budget.doTransfer(groupCategories.map(c => c.id), transferId); await budget.doTransfer(
groupCategories.map(c => c.id),
transferId
);
} }
await db.deleteCategoryGroup({ id }, transferId); await db.deleteCategoryGroup({ id }, transferId);
}); });
@ -759,11 +771,15 @@ handlers['accounts-get'] = async function() {
}; };
handlers['account-properties'] = async function({ id }) { handlers['account-properties'] = async function({ id }) {
const { balance } = await db.first( const {
balance
} = await db.first(
'SELECT sum(amount) as balance FROM transactions WHERE acct = ? AND isParent = 0 AND tombstone = 0', 'SELECT sum(amount) as balance FROM transactions WHERE acct = ? AND isParent = 0 AND tombstone = 0',
[id] [id]
); );
const { count } = await db.first( const {
count
} = await db.first(
'SELECT count(id) as count FROM transactions WHERE acct = ? AND tombstone = 0', 'SELECT count(id) as count FROM transactions WHERE acct = ? AND tombstone = 0',
[id] [id]
); );
@ -902,10 +918,9 @@ handlers['account-close'] = mutator(async function({
true true
); );
let { id: payeeId } = await db.first( let {
'SELECT id FROM payees WHERE transfer_acct = ?', id: payeeId
[id] } = await db.first('SELECT id FROM payees WHERE transfer_acct = ?', [id]);
);
await batchMessages(() => { await batchMessages(() => {
// TODO: what this should really do is send a special message that // TODO: what this should really do is send a special message that
@ -939,10 +954,11 @@ handlers['account-close'] = mutator(async function({
// If there is a balance we need to transfer it to the specified // If there is a balance we need to transfer it to the specified
// account (and possibly categorize it) // account (and possibly categorize it)
if (balance !== 0) { if (balance !== 0) {
let { id: payeeId } = await db.first( let {
'SELECT id FROM payees WHERE transfer_acct = ?', id: payeeId
[transferAccountId] } = await db.first('SELECT id FROM payees WHERE transfer_acct = ?', [
); transferAccountId
]);
await handlers['transaction-add']({ await handlers['transaction-add']({
id: uuid.v4Sync(), id: uuid.v4Sync(),
@ -1082,9 +1098,7 @@ handlers['accounts-sync'] = async function({ id }) {
} else if (err instanceof PostError && err.reason !== 'internal') { } else if (err instanceof PostError && err.reason !== 'internal') {
errors.push({ errors.push({
accountId: acct.id, accountId: acct.id,
message: `Account "${ message: `Account "${acct.name}" is not linked properly. Please link it again`
acct.name
}" is not linked properly. Please link it again`
}); });
} else { } else {
errors.push({ errors.push({
@ -1134,10 +1148,9 @@ handlers['transactions-import'] = mutator(function({
}); });
handlers['account-unlink'] = mutator(async function({ id }) { handlers['account-unlink'] = mutator(async function({ id }) {
let { bank: bankId } = await db.first( let {
'SELECT bank FROM accounts WHERE id = ?', bank: bankId
[id] } = await db.first('SELECT bank FROM accounts WHERE id = ?', [id]);
);
if (!bankId) { if (!bankId) {
return 'ok'; return 'ok';
@ -1152,10 +1165,11 @@ handlers['account-unlink'] = mutator(async function({ id }) {
balance_limit: null balance_limit: null
}); });
let { count } = await db.first( let {
'SELECT COUNT(*) as count FROM accounts WHERE bank = ?', count
[bankId] } = await db.first('SELECT COUNT(*) as count FROM accounts WHERE bank = ?', [
); bankId
]);
if (count === 0) { if (count === 0) {
// No more accounts are associated with this bank. We can remove // No more accounts are associated with this bank. We can remove
@ -1545,7 +1559,8 @@ handlers['get-version'] = async function() {
handlers['get-budgets'] = async function() { handlers['get-budgets'] = async function() {
const paths = await fs.listDir(fs.getDocumentDir()); const paths = await fs.listDir(fs.getDocumentDir());
const budgets = (await Promise.all( const budgets = (
await Promise.all(
paths.map(async name => { paths.map(async name => {
const prefsPath = fs.join(fs.getDocumentDir(), name, 'metadata.json'); const prefsPath = fs.join(fs.getDocumentDir(), name, 'metadata.json');
if (await fs.exists(prefsPath)) { if (await fs.exists(prefsPath)) {
@ -1573,7 +1588,8 @@ handlers['get-budgets'] = async function() {
return null; return null;
}) })
)).filter(x => x); )
).filter(x => x);
return budgets; return budgets;
}; };
@ -2253,7 +2269,13 @@ export const lib = {
// Expose CRDT mechanisms so server can use them // Expose CRDT mechanisms so server can use them
merkle, merkle,
timestamp: { timestamp: {
getClock, setClock, makeClock, makeClientId, serializeClock, deserializeClock, Timestamp getClock,
setClock,
makeClock,
makeClientId,
serializeClock,
deserializeClock,
Timestamp
}, },
SyncProtoBuf: SyncPb SyncProtoBuf: SyncPb
}; };

View file

@ -1,2 +1,2 @@
// Mobile needs this // Mobile needs this
import 'core-js/modules/es.object.from-entries' import 'core-js/modules/es.object.from-entries';

View file

@ -71,7 +71,10 @@ describe('schedule app', () => {
value: { value: {
start: '2020-12-20', start: '2020-12-20',
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: 'day', value: 15 }, { type: 'day', value: 30 }] patterns: [
{ type: 'day', value: 15 },
{ type: 'day', value: 30 }
]
} }
}) })
).toBe('2021-05-30'); ).toBe('2021-05-30');
@ -88,7 +91,10 @@ describe('schedule app', () => {
value: { value: {
start: '2020-12-20', start: '2020-12-20',
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: 'day', value: 15 }, { type: 'day', value: 30 }] patterns: [
{ type: 'day', value: 15 },
{ type: 'day', value: 30 }
]
} }
} }
] ]
@ -123,7 +129,10 @@ describe('schedule app', () => {
value: { value: {
start: '2020-12-20', start: '2020-12-20',
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: 'day', value: 15 }, { type: 'day', value: 30 }] patterns: [
{ type: 'day', value: 15 },
{ type: 'day', value: 30 }
]
} }
} }
] ]
@ -150,7 +159,10 @@ describe('schedule app', () => {
value: { value: {
start: '2020-12-20', start: '2020-12-20',
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: 'day', value: 18 }, { type: 'day', value: 29 }] patterns: [
{ type: 'day', value: 18 },
{ type: 'day', value: 29 }
]
} }
} }
] ]
@ -177,7 +189,10 @@ describe('schedule app', () => {
value: { value: {
start: '2020-12-20', start: '2020-12-20',
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: 'day', value: 15 }, { type: 'day', value: 30 }] patterns: [
{ type: 'day', value: 15 },
{ type: 'day', value: 30 }
]
} }
} }
] ]
@ -200,7 +215,10 @@ describe('schedule app', () => {
value: { value: {
start: '2020-12-20', start: '2020-12-20',
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: 'day', value: 15 }, { type: 'day', value: 30 }] patterns: [
{ type: 'day', value: 15 },
{ type: 'day', value: 30 }
]
} }
} }
] ]
@ -222,7 +240,10 @@ describe('schedule app', () => {
value: { value: {
start: '2020-12-20', start: '2020-12-20',
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: 'day', value: 18 }, { type: 'day', value: 28 }] patterns: [
{ type: 'day', value: 18 },
{ type: 'day', value: 28 }
]
} }
} }
] ]

View file

@ -219,7 +219,10 @@ async function monthly1stor3rd(startDate, accountId) {
return { return {
start, start,
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: dayValue, value: 1 }, { type: dayValue, value: 3 }] patterns: [
{ type: dayValue, value: 1 },
{ type: dayValue, value: 3 }
]
}; };
}, },
accountId accountId
@ -237,7 +240,10 @@ async function monthly2ndor4th(startDate, accountId) {
return { return {
start, start,
frequency: 'monthly', frequency: 'monthly',
patterns: [{ type: dayValue, value: 2 }, { type: dayValue, value: 4 }] patterns: [
{ type: dayValue, value: 2 },
{ type: dayValue, value: 4 }
]
}; };
}, },
accountId accountId

View file

@ -363,7 +363,7 @@ function parsePostfix(state, node) {
while ((tok = nextToken(state))) { while ((tok = nextToken(state))) {
if (tok.type === types.TOKEN_LEFT_PAREN) { if (tok.type === types.TOKEN_LEFT_PAREN) {
pushToken(state, tok); pushToken(state, tok);
let args = parseArgs(state) let args = parseArgs(state);
node = new nodes.FunCall(tok.lineno, tok.colno, node, args); node = new nodes.FunCall(tok.lineno, tok.colno, node, args);
} else if (tok.type === types.TOKEN_DOT) { } else if (tok.type === types.TOKEN_DOT) {
const val = nextToken(state); const val = nextToken(state);

View file

@ -238,9 +238,7 @@ export default function generate(table, where, groupby, select, deps) {
joins.push(meta.sql(lookup.tableId)); joins.push(meta.sql(lookup.tableId));
} else { } else {
joins.push( joins.push(
`LEFT JOIN ${meta.table} ${lookup.tableId} ON ${ `LEFT JOIN ${meta.table} ${lookup.tableId} ON ${lookup.tableId}.id = ${currentTable.id}.${lookup.field}`
lookup.tableId
}.id = ${currentTable.id}.${lookup.field}`
); );
} }

View file

@ -74,7 +74,10 @@ export default class VM {
call(callee, args) { call(callee, args) {
const func = this.get(callee); const func = this.get(callee);
this.reg1 = func.apply(null, args.map(arg => this.get(arg))); this.reg1 = func.apply(
null,
args.map(arg => this.get(arg))
);
} }
query(sql, calculated) { query(sql, calculated) {

View file

@ -1,10 +1,8 @@
const expect = require('expect');
const propagate = require('../data-compute/propagate.js');
describe('data propagation', () => {
const expect = require("expect"); it('should work', () => {
const propagate = require("../data-compute/propagate.js");
describe("data propagation", () => {
it("should work", () => {
expect(true).toExist(); expect(true).toExist();
}); });
}); });

View file

@ -1,291 +1,281 @@
// Unit tests for reactive-property. // Unit tests for reactive-property.
var assert = require("assert"); var assert = require('assert');
// If using from the NPM package, this line would be // If using from the NPM package, this line would be
// var Graph = require("graph-data-structure"); // var Graph = require("graph-data-structure");
var Graph = require("../data-compute/graph-data-structure"); var Graph = require('../data-compute/graph-data-structure');
describe("Graph", function() { describe('Graph', function() {
describe("Data structure", function() { describe('Data structure', function() {
it("Should add nodes and list them.", function (){ it('Should add nodes and list them.', function() {
var graph = Graph(); var graph = Graph();
graph.addNode("a"); graph.addNode('a');
graph.addNode("b"); graph.addNode('b');
assert.equal(graph.nodes().length, 2); assert.equal(graph.nodes().length, 2);
assert(contains(graph.nodes(), "a")); assert(contains(graph.nodes(), 'a'));
assert(contains(graph.nodes(), "b")); assert(contains(graph.nodes(), 'b'));
}); });
it("Should chain addNode.", function (){ it('Should chain addNode.', function() {
var graph = Graph().addNode("a").addNode("b"); var graph = Graph()
.addNode('a')
.addNode('b');
assert.equal(graph.nodes().length, 2); assert.equal(graph.nodes().length, 2);
assert(contains(graph.nodes(), "a")); assert(contains(graph.nodes(), 'a'));
assert(contains(graph.nodes(), "b")); assert(contains(graph.nodes(), 'b'));
}); });
it("Should remove nodes.", function (){ it('Should remove nodes.', function() {
var graph = Graph(); var graph = Graph();
graph.addNode("a"); graph.addNode('a');
graph.addNode("b"); graph.addNode('b');
graph.removeNode("a"); graph.removeNode('a');
graph.removeNode("b"); graph.removeNode('b');
assert.equal(graph.nodes().length, 0); assert.equal(graph.nodes().length, 0);
}); });
it("Should chain removeNode.", function (){ it('Should chain removeNode.', function() {
var graph = Graph() var graph = Graph()
.addNode("a") .addNode('a')
.addNode("b") .addNode('b')
.removeNode("a") .removeNode('a')
.removeNode("b"); .removeNode('b');
assert.equal(graph.nodes().length, 0); assert.equal(graph.nodes().length, 0);
}); });
it("Should add edges and query for adjacent nodes.", function (){ it('Should add edges and query for adjacent nodes.', function() {
var graph = Graph(); var graph = Graph();
graph.addNode("a"); graph.addNode('a');
graph.addNode("b"); graph.addNode('b');
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
assert.equal(graph.adjacent("a").length, 1); assert.equal(graph.adjacent('a').length, 1);
assert.equal(graph.adjacent("a")[0], "b"); assert.equal(graph.adjacent('a')[0], 'b');
}); });
it("Should implicitly add nodes when edges are added.", function (){ it('Should implicitly add nodes when edges are added.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
assert.equal(graph.adjacent("a").length, 1); assert.equal(graph.adjacent('a').length, 1);
assert.equal(graph.adjacent("a")[0], "b"); assert.equal(graph.adjacent('a')[0], 'b');
assert.equal(graph.nodes().length, 2); assert.equal(graph.nodes().length, 2);
assert(contains(graph.nodes(), "a")); assert(contains(graph.nodes(), 'a'));
assert(contains(graph.nodes(), "b")); assert(contains(graph.nodes(), 'b'));
}); });
it("Should chain addEdge.", function (){ it('Should chain addEdge.', function() {
var graph = Graph().addEdge("a", "b"); var graph = Graph().addEdge('a', 'b');
assert.equal(graph.adjacent("a").length, 1); assert.equal(graph.adjacent('a').length, 1);
assert.equal(graph.adjacent("a")[0], "b"); assert.equal(graph.adjacent('a')[0], 'b');
}); });
it("Should remove edges.", function (){ it('Should remove edges.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
graph.removeEdge("a", "b"); graph.removeEdge('a', 'b');
assert.equal(graph.adjacent("a").length, 0); assert.equal(graph.adjacent('a').length, 0);
}); });
it("Should chain removeEdge.", function (){ it('Should chain removeEdge.', function() {
var graph = Graph() var graph = Graph()
.addEdge("a", "b") .addEdge('a', 'b')
.removeEdge("a", "b"); .removeEdge('a', 'b');
assert.equal(graph.adjacent("a").length, 0); assert.equal(graph.adjacent('a').length, 0);
}); });
it("Should not remove nodes when edges are removed.", function (){ it('Should not remove nodes when edges are removed.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
graph.removeEdge("a", "b"); graph.removeEdge('a', 'b');
assert.equal(graph.nodes().length, 2); assert.equal(graph.nodes().length, 2);
assert(contains(graph.nodes(), "a")); assert(contains(graph.nodes(), 'a'));
assert(contains(graph.nodes(), "b")); assert(contains(graph.nodes(), 'b'));
}); });
it("Should remove outgoing edges when a node is removed.", function (){ it('Should remove outgoing edges when a node is removed.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
graph.removeNode("a"); graph.removeNode('a');
assert.equal(graph.adjacent("a").length, 0); assert.equal(graph.adjacent('a').length, 0);
}); });
it("Should remove incoming edges when a node is removed.", function (){ it('Should remove incoming edges when a node is removed.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
graph.removeNode("b"); graph.removeNode('b');
assert.equal(graph.adjacent("a").length, 0); assert.equal(graph.adjacent('a').length, 0);
}); });
it("Should compute indegree.", function (){ it('Should compute indegree.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
assert.equal(graph.indegree("a"), 0); assert.equal(graph.indegree('a'), 0);
assert.equal(graph.indegree("b"), 1); assert.equal(graph.indegree('b'), 1);
graph.addEdge("c", "b"); graph.addEdge('c', 'b');
assert.equal(graph.indegree("b"), 2); assert.equal(graph.indegree('b'), 2);
}); });
it("Should compute outdegree.", function (){ it('Should compute outdegree.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
assert.equal(graph.outdegree("a"), 1); assert.equal(graph.outdegree('a'), 1);
assert.equal(graph.outdegree("b"), 0); assert.equal(graph.outdegree('b'), 0);
graph.addEdge("a", "c"); graph.addEdge('a', 'c');
assert.equal(graph.outdegree("a"), 2); assert.equal(graph.outdegree('a'), 2);
});
}); });
}); describe('Algorithms', function() {
describe("Algorithms", function() {
// This example is from Cormen et al. "Introduction to Algorithms" page 550 // This example is from Cormen et al. "Introduction to Algorithms" page 550
it("Should compute topological sort.", function (){ it('Should compute topological sort.', function() {
var graph = Graph(); var graph = Graph();
// Shoes depend on socks. // Shoes depend on socks.
// Socks need to be put on before shoes. // Socks need to be put on before shoes.
graph.addEdge("socks", "shoes"); graph.addEdge('socks', 'shoes');
graph.addEdge("shirt", "belt"); graph.addEdge('shirt', 'belt');
graph.addEdge("shirt", "tie"); graph.addEdge('shirt', 'tie');
graph.addEdge("tie", "jacket"); graph.addEdge('tie', 'jacket');
graph.addEdge("belt", "jacket"); graph.addEdge('belt', 'jacket');
graph.addEdge("pants", "shoes"); graph.addEdge('pants', 'shoes');
graph.addEdge("underpants", "pants"); graph.addEdge('underpants', 'pants');
graph.addEdge("pants", "belt"); graph.addEdge('pants', 'belt');
var sorted = graph.topologicalSort(); var sorted = graph.topologicalSort();
assert(comesBefore(sorted, "pants", "shoes")); assert(comesBefore(sorted, 'pants', 'shoes'));
assert(comesBefore(sorted, "underpants", "pants")); assert(comesBefore(sorted, 'underpants', 'pants'));
assert(comesBefore(sorted, "underpants", "shoes")); assert(comesBefore(sorted, 'underpants', 'shoes'));
assert(comesBefore(sorted, "shirt", "jacket")); assert(comesBefore(sorted, 'shirt', 'jacket'));
assert(comesBefore(sorted, "shirt", "belt")); assert(comesBefore(sorted, 'shirt', 'belt'));
assert(comesBefore(sorted, "belt", "jacket")); assert(comesBefore(sorted, 'belt', 'jacket'));
assert.equal(sorted.length, 8); assert.equal(sorted.length, 8);
}); });
it("Should compute topological sort, excluding source nodes.", function (){ it('Should compute topological sort, excluding source nodes.', function() {
var graph = Graph(); var graph = Graph();
graph.addEdge("a", "b"); graph.addEdge('a', 'b');
graph.addEdge("b", "c"); graph.addEdge('b', 'c');
var sorted = graph.topologicalSort(["a"], false); var sorted = graph.topologicalSort(['a'], false);
assert.equal(sorted.length, 2); assert.equal(sorted.length, 2);
assert.equal(sorted[0], "b"); assert.equal(sorted[0], 'b');
assert.equal(sorted[1], "c"); assert.equal(sorted[1], 'c');
}); });
it("Should compute topological sort tricky case.", function (){ it('Should compute topological sort tricky case.', function() {
var graph = Graph(); // a var graph = Graph(); // a
// / \ // / \
graph.addEdge("a", "b"); // b | graph.addEdge('a', 'b'); // b |
graph.addEdge("a", "d"); // | d graph.addEdge('a', 'd'); // | d
graph.addEdge("b", "c"); // c | graph.addEdge('b', 'c'); // c |
graph.addEdge("d", "e"); // \ / graph.addEdge('d', 'e'); // \ /
graph.addEdge("c", "e"); // e graph.addEdge('c', 'e'); // e
var sorted = graph.topologicalSort(["a"], false); var sorted = graph.topologicalSort(['a'], false);
assert.equal(sorted.length, 4); assert.equal(sorted.length, 4);
assert(contains(sorted, "b")); assert(contains(sorted, 'b'));
assert(contains(sorted, "c")); assert(contains(sorted, 'c'));
assert(contains(sorted, "d")); assert(contains(sorted, 'd'));
assert.equal(sorted[sorted.length - 1], "e"); assert.equal(sorted[sorted.length - 1], 'e');
assert(comesBefore(sorted, "b", "c"));
assert(comesBefore(sorted, "b", "e"));
assert(comesBefore(sorted, "c", "e"));
assert(comesBefore(sorted, "d", "e"));
assert(comesBefore(sorted, 'b', 'c'));
assert(comesBefore(sorted, 'b', 'e'));
assert(comesBefore(sorted, 'c', 'e'));
assert(comesBefore(sorted, 'd', 'e'));
}); });
it("Should exclude source nodes with a cycle.", function (){ it('Should exclude source nodes with a cycle.', function() {
var graph = Graph() var graph = Graph()
.addEdge("a", "b") .addEdge('a', 'b')
.addEdge("b", "c") .addEdge('b', 'c')
.addEdge("c", "a"); .addEdge('c', 'a');
var sorted = graph.topologicalSort(["a"], false); var sorted = graph.topologicalSort(['a'], false);
assert.equal(sorted.length, 2); assert.equal(sorted.length, 2);
assert.equal(sorted[0], "b"); assert.equal(sorted[0], 'b');
assert.equal(sorted[1], "c"); assert.equal(sorted[1], 'c');
}); });
it("Should exclude source nodes with multiple cycles.", function (){ it('Should exclude source nodes with multiple cycles.', function() {
var graph = Graph() var graph = Graph()
.addEdge('a', 'b')
.addEdge('b', 'a')
.addEdge("a", "b") .addEdge('b', 'c')
.addEdge("b", "a") .addEdge('c', 'b')
.addEdge("b", "c") .addEdge('a', 'c')
.addEdge("c", "b") .addEdge('c', 'a');
.addEdge("a", "c") var sorted = graph.topologicalSort(['a', 'b'], false);
.addEdge("c", "a"); assert(!contains(sorted, 'b'));
var sorted = graph.topologicalSort(["a", "b"], false);
assert(!contains(sorted, "b"));
}); });
}); });
describe("Edge cases and error handling", function() { describe('Edge cases and error handling', function() {
it('Should return empty array of adjacent nodes for unknown nodes.', function() {
it("Should return empty array of adjacent nodes for unknown nodes.", function (){
var graph = Graph(); var graph = Graph();
assert.equal(graph.adjacent("a").length, 0); assert.equal(graph.adjacent('a').length, 0);
assert.equal(graph.nodes(), 0); assert.equal(graph.nodes(), 0);
}); });
it("Should do nothing if removing an edge that does not exist.", function (){ it('Should do nothing if removing an edge that does not exist.', function() {
assert.doesNotThrow(function() { assert.doesNotThrow(function() {
var graph = Graph(); var graph = Graph();
graph.removeEdge("a", "b"); graph.removeEdge('a', 'b');
}); });
}); });
it("Should return indegree of 0 for unknown nodes.", function (){ it('Should return indegree of 0 for unknown nodes.', function() {
var graph = Graph(); var graph = Graph();
assert.equal(graph.indegree("z"), 0); assert.equal(graph.indegree('z'), 0);
}); });
it("Should return outdegree of 0 for unknown nodes.", function (){ it('Should return outdegree of 0 for unknown nodes.', function() {
var graph = Graph(); var graph = Graph();
assert.equal(graph.outdegree("z"), 0); assert.equal(graph.outdegree('z'), 0);
});
}); });
}); describe('Serialization', function() {
describe("Serialization", function() {
var serialized; var serialized;
function checkSerialized(graph) { function checkSerialized(graph) {
assert.equal(graph.nodes.length, 3); assert.equal(graph.nodes.length, 3);
assert.equal(graph.links.length, 2); assert.equal(graph.links.length, 2);
assert.equal(graph.nodes[0].id, "a"); assert.equal(graph.nodes[0].id, 'a');
assert.equal(graph.nodes[1].id, "b"); assert.equal(graph.nodes[1].id, 'b');
assert.equal(graph.nodes[2].id, "c"); assert.equal(graph.nodes[2].id, 'c');
assert.equal(graph.links[0].source, "a"); assert.equal(graph.links[0].source, 'a');
assert.equal(graph.links[0].target, "b"); assert.equal(graph.links[0].target, 'b');
assert.equal(graph.links[1].source, "b"); assert.equal(graph.links[1].source, 'b');
assert.equal(graph.links[1].target, "c"); assert.equal(graph.links[1].target, 'c');
} }
it("Should serialize a graph.", function (){ it('Should serialize a graph.', function() {
var graph = Graph() var graph = Graph()
.addEdge("a", "b") .addEdge('a', 'b')
.addEdge("b", "c"); .addEdge('b', 'c');
serialized = graph.serialize(); serialized = graph.serialize();
checkSerialized(serialized); checkSerialized(serialized);
}); });
it("Should deserialize a graph.", function (){ it('Should deserialize a graph.', function() {
var graph = Graph(); var graph = Graph();
graph.deserialize(serialized); graph.deserialize(serialized);
checkSerialized(graph.serialize()); checkSerialized(graph.serialize());
}); });
it("Should chain deserialize a graph.", function (){ it('Should chain deserialize a graph.', function() {
var graph = Graph().deserialize(serialized); var graph = Graph().deserialize(serialized);
checkSerialized(graph.serialize()); checkSerialized(graph.serialize());
}); });
it("Should deserialize a graph passed to constructor.", function (){ it('Should deserialize a graph passed to constructor.', function() {
var graph = Graph(serialized); var graph = Graph(serialized);
checkSerialized(graph.serialize()); checkSerialized(graph.serialize());
}); });
@ -293,16 +283,22 @@ describe("Graph", function() {
}); });
function contains(arr, item) { function contains(arr, item) {
return arr.filter(function (d){ return (
arr.filter(function(d) {
return d === item; return d === item;
}).length > 0; }).length > 0
);
} }
function comesBefore(arr, a, b) { function comesBefore(arr, a, b) {
var aIndex, bIndex; var aIndex, bIndex;
arr.forEach(function(d, i) { arr.forEach(function(d, i) {
if(d === a){ aIndex = i; } if (d === a) {
if(d === b){ bIndex = i; } aIndex = i;
}
if (d === b) {
bIndex = i;
}
}); });
return aIndex < bIndex; return aIndex < bIndex;
} }

View file

@ -1,12 +1,12 @@
const sqlite = require("sqlite3"); const sqlite = require('sqlite3');
const escodegen = require("escodegen"); const escodegen = require('escodegen');
const sqlgen = require("./sqlgen"); const sqlgen = require('./sqlgen');
// Example usage: // Example usage:
const Spreadsheet = require("./spreadsheet"); const Spreadsheet = require('./spreadsheet');
const db = new sqlite.Database(__dirname + "/../../db.sqlite"); const db = new sqlite.Database(__dirname + '/../../db.sqlite');
const sheet = new Spreadsheet({ const sheet = new Spreadsheet({
plugins: { plugins: {
runQuery: { runQuery: {
@ -20,22 +20,24 @@ const sheet = new Spreadsheet({
return { return {
data: { data: {
type: "query", type: 'query',
query: query, query: query,
sql: sql sql: sql
}, },
ast: { ast: {
type: "CallExpression", type: 'CallExpression',
callee: { callee: {
type: "Identifier", type: 'Identifier',
name: "runQuery" name: 'runQuery'
}, },
arguments: [{ arguments: [
type: "Literal", {
type: 'Literal',
raw: sql, raw: sql,
value: sql value: sql
}] }
]
} }
}; };
}, },
@ -54,11 +56,12 @@ const sheet = new Spreadsheet({
} }
}); });
db.on("preupdate", function(type, dbname, table, old, _new, oldId, newId) { db.on('preupdate', function(type, dbname, table, old, _new, oldId, newId) {
sheet.resolve().then(() => { sheet.resolve().then(() => {
const start = Date.now(); const start = Date.now();
sheet.startTransaction(); sheet.startTransaction();
sheet.getNodesOfType("query") sheet
.getNodesOfType('query')
.filter(node => node.data.query.table === table) .filter(node => node.data.query.table === table)
.forEach(q => { .forEach(q => {
sheet.signal(q.name); sheet.signal(q.name);

View file

@ -2,13 +2,13 @@ import * as prefs from '../prefs';
import * as db from '../db'; import * as db from '../db';
import * as sheet from '../sheet'; import * as sheet from '../sheet';
import * as sync from './index'; import * as sync from './index';
import { getClock, Timestamp } from '../crdt'; import { merkle, getClock, Timestamp } from '../crdt';
import { merkle } from '../crdt';
import * as encoder from './encoder'; import * as encoder from './encoder';
const jsc = require('jsverify'); const jsc = require('jsverify');
const uuidGenerator = jsc const uuidGenerator = jsc.integer(97, 122).smap(
.integer(97, 122) x => String.fromCharCode(x),
.smap(x => String.fromCharCode(x), x => x.charCodeAt(x)); x => x.charCodeAt(x)
);
const mockSyncServer = require('../tests/mockSyncServer'); const mockSyncServer = require('../tests/mockSyncServer');
@ -126,7 +126,10 @@ Object.keys(schema).forEach(table => {
generators.push( generators.push(
makeGen({ makeGen({
table, table,
row: jsc.asciinestring.smap(x => 'sheet!' + x, x => x), row: jsc.asciinestring.smap(
x => 'sheet!' + x,
x => x
),
field: 'expr', field: 'expr',
value: jsc.constant(JSON.stringify('fooooo')) value: jsc.constant(JSON.stringify('fooooo'))
}) })

View file

@ -24,11 +24,9 @@ function isAnonymous(id) {
return !id.startsWith('user-'); return !id.startsWith('user-');
} }
export async function init() { export async function init() {}
}
export async function login(userId) { export async function login(userId) {}
}
let BUFFERING = false; let BUFFERING = false;
let BUFFER = []; let BUFFER = [];

View file

@ -1888,7 +1888,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/types@npm:^7.18.10, @babel/types@npm:^7.18.13, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.8.3": "@babel/types@npm:^7.18.10, @babel/types@npm:^7.18.13, @babel/types@npm:^7.18.9, @babel/types@npm:^7.8.3":
version: 7.18.13 version: 7.18.13
resolution: "@babel/types@npm:7.18.13" resolution: "@babel/types@npm:7.18.13"
dependencies: dependencies:
@ -1899,6 +1899,16 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/types@npm:^7.18.6":
version: 7.18.8
resolution: "@babel/types@npm:7.18.8"
dependencies:
"@babel/helper-validator-identifier": ^7.18.6
to-fast-properties: ^2.0.0
checksum: a485531faa9ff3b83ea94ba6502321dd66e39202c46d7765e4336cb4aff2ff69ebc77d97b17e21331a8eedde1f5490ce00e8a430c1041fc26854d636e6701919
languageName: node
linkType: hard
"@bcoe/v8-coverage@npm:^0.2.3": "@bcoe/v8-coverage@npm:^0.2.3":
version: 0.2.3 version: 0.2.3
resolution: "@bcoe/v8-coverage@npm:0.2.3" resolution: "@bcoe/v8-coverage@npm:0.2.3"
@ -9533,6 +9543,21 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"eslint-plugin-prettier@npm:^3.1.4":
version: 3.4.1
resolution: "eslint-plugin-prettier@npm:3.4.1"
dependencies:
prettier-linter-helpers: ^1.0.0
peerDependencies:
eslint: ">=5.0.0"
prettier: ">=1.13.0"
peerDependenciesMeta:
eslint-config-prettier:
optional: true
checksum: fa6a89f0d7cba1cc87064352f5a4a68dc3739448dd279bec2bced1bfa3b704467e603d13b69dcec853f8fa30b286b8b715912898e9da776e1b016cf0ee48bd99
languageName: node
linkType: hard
"eslint-plugin-react@npm:7.11.1": "eslint-plugin-react@npm:7.11.1":
version: 7.11.1 version: 7.11.1
resolution: "eslint-plugin-react@npm:7.11.1" resolution: "eslint-plugin-react@npm:7.11.1"
@ -10065,6 +10090,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"fast-diff@npm:^1.1.2":
version: 1.2.0
resolution: "fast-diff@npm:1.2.0"
checksum: 1b5306eaa9e826564d9e5ffcd6ebd881eb5f770b3f977fcbf38f05c824e42172b53c79920e8429c54eb742ce15a0caf268b0fdd5b38f6de52234c4a8368131ae
languageName: node
linkType: hard
"fast-glob@npm:^2.2.0, fast-glob@npm:^2.2.2": "fast-glob@npm:^2.2.0, fast-glob@npm:^2.2.2":
version: 2.2.7 version: 2.2.7
resolution: "fast-glob@npm:2.2.7" resolution: "fast-glob@npm:2.2.7"
@ -14661,6 +14693,7 @@ jest-snapshot@test:
date-fns: 2.0.0-alpha.27 date-fns: 2.0.0-alpha.27
deep-equal: ^2.0.5 deep-equal: ^2.0.5
eslint: 5.6.0 eslint: 5.6.0
eslint-plugin-prettier: ^3.1.4
esm: ^3.0.82 esm: ^3.0.82
fake-indexeddb: ^3.1.3 fake-indexeddb: ^3.1.3
fast-check: 2.13.0 fast-check: 2.13.0
@ -14679,6 +14712,7 @@ jest-snapshot@test:
node-fetch: ^1.6.3 node-fetch: ^1.6.3
node-libofx: "*" node-libofx: "*"
perf-deets: ^1.0.15 perf-deets: ^1.0.15
prettier: ^1.19.1
regenerator-runtime: ^0.13.7 regenerator-runtime: ^0.13.7
sanitize-filename: ^1.6.1 sanitize-filename: ^1.6.1
search-query-parser: ^1.3.0 search-query-parser: ^1.3.0
@ -18438,7 +18472,16 @@ jest-snapshot@test:
languageName: node languageName: node
linkType: hard linkType: hard
"prettier@npm:^1.14.2, prettier@npm:^1.18.1": "prettier-linter-helpers@npm:^1.0.0":
version: 1.0.0
resolution: "prettier-linter-helpers@npm:1.0.0"
dependencies:
fast-diff: ^1.1.2
checksum: 00ce8011cf6430158d27f9c92cfea0a7699405633f7f1d4a45f07e21bf78e99895911cbcdc3853db3a824201a7c745bd49bfea8abd5fb9883e765a90f74f8392
languageName: node
linkType: hard
"prettier@npm:^1.14.2, prettier@npm:^1.18.1, prettier@npm:^1.19.1":
version: 1.19.1 version: 1.19.1
resolution: "prettier@npm:1.19.1" resolution: "prettier@npm:1.19.1"
bin: bin: