2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 14:50:56 +00:00

Merge pull request #674 from frappe/uitest

test: add some simple UI test, format files using prettier
This commit is contained in:
Alan 2023-06-27 02:33:55 -07:00 committed by GitHub
commit a3590bbc5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 4161 additions and 4870 deletions

View File

@ -50,6 +50,7 @@ module.exports = {
],
ignorePatterns: [
'*.mjs',
'uitest',
'.eslintrc.js',
'tailwind.config.js',
'node_modules',

View File

@ -1,2 +1,5 @@
# Rename 'frappe' to 'fyo' outside src
32d282dc9c6f129807a1cf53eae47fc3602aa976
32d282dc9c6f129807a1cf53eae47fc3602aa976
# Format files using prettier
8c9d81d298dd08ae7acaf6de297aa30d95329778

View File

@ -29,4 +29,7 @@ jobs:
run: yarn
- name: Lint
run: yarn lint
run: yarn lint
- name: Check Formatting
run: yarn prettier --check .

View File

@ -28,5 +28,28 @@ jobs:
- name: Install Dependencies
run: yarn
- name: Yarn Tests
run: yarn test
- name: Run Tests
run: yarn test
setup_and_uitest:
runs-on: macos-11
steps:
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: '16.14.0'
- name: Set yarn version
run: yarn set version 1.22.18
- name: Checkout Books
uses: actions/checkout@v2
- name: Install Dependencies
run: yarn
- name: Build Source Files
run: yarn build --nosign --nopackage
- name: Run UI Tests
run: yarn uitest

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.DS_Store
node_modules
/dbs
/dist
/notebook

View File

@ -1 +1,3 @@
**/types.ts
**/types.ts
**/dist_electron
**/dummy/*.json

View File

@ -97,7 +97,6 @@ couple of seconds to render this because of how dev mode works. Each file is
individually served by the dev server. And there are many files that have to be
sent.
**Note: Debug Electron Main Process**
When in dev mode electron runs with the `--inspect` flag which allows an

View File

@ -8,7 +8,7 @@ import {
assertDoesNotThrow,
assertThrows,
BaseMetaKey,
getBuiltTestSchemaMap
getBuiltTestSchemaMap,
} from './helpers';
/**

View File

@ -16,11 +16,29 @@ const packageDirPath = path.join(root, 'dist_electron', 'bundled');
const mainFileName = 'main.js';
const commonConfig = getMainProcessCommonConfig(root);
const rawArgs = yargs(hideBin(process.argv))
.option('nosign', {
type: 'boolean',
description: 'Run electron-builder without code signing',
})
.option('nopackage', {
type: 'boolean',
description: 'Only build the source files, electron-builder will not run',
});
const argv = rawArgs.argv;
if (argv.nosign) {
process.env['CSC_IDENTITY_AUTO_DISCOVERY'] = false;
}
updatePaths();
await buildMainProcessSource();
await buildRendererProcessSource();
copyPackageJson();
await packageApp();
if (!argv.nopackage) {
await packageApp();
}
function updatePaths() {
fs.removeSync(buildDirPath);
@ -117,8 +135,8 @@ function copyPackageJson() {
* Packages the app using electron builder.
*
* Note: this also handles signing and notarization if the
* appropriate flags are set.
*
* appropriate flags are set.
*
* Electron builder cli [commands](https://www.electron.build/cli)
* are passed on as builderArgs.
*/
@ -127,11 +145,14 @@ async function packageApp() {
'electron-builder/out/builder.js'
);
const rawArgs = hideBin(process.argv);
const builderArgs = yargs(rawArgs)
const builderArgs = rawArgs
.command(['build', '*'], 'Build', configureBuildCommand)
.parse();
for (const opt of ['nosign', 'nopackage']) {
delete builderArgs[opt];
}
const buildOptions = {
config: {
directories: { output: packageDirPath, app: buildDirPath },

View File

@ -8,4 +8,4 @@ these are not to be edited.
The generated data has some randomness, there is a `baseCount` arg to the
exported `setupDummyInstance` function, the number of transactions generated are
always more than this.
always more than this.

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,3 @@
{
"countryCode": "ae",
"name": "U.A.E - Chart of Accounts",
@ -464,4 +463,4 @@
"rootType": "Equity"
}
}
}
}

View File

@ -409,4 +409,4 @@
"rootType": "Income"
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -420,4 +420,4 @@
"rootType": "Asset"
}
}
}
}

View File

@ -832,4 +832,4 @@
"rootType": "Income"
}
}
}
}

View File

@ -684,4 +684,4 @@
"rootType": "Income"
}
}
}
}

View File

@ -173,4 +173,4 @@
"rootType": "Liability"
}
}
}
}

View File

@ -599,4 +599,4 @@
"rootType": "Liability"
}
}
}
}

View File

@ -491,4 +491,4 @@
"rootType": "Liability"
}
}
}
}

View File

@ -690,4 +690,4 @@
"rootType": "Asset"
}
}
}
}

View File

@ -308,4 +308,4 @@
"rootType": "Liability"
}
}
}
}

View File

@ -1,170 +1,170 @@
{
"Application of Funds (Assets)": {
"Current Assets": {
"Accounts Receivable": {
"Debtors": {
"accountType": "Receivable"
}
},
"Bank Accounts": {
"accountType": "Bank",
"isGroup": true
},
"Cash In Hand": {
"Cash": {
"accountType": "Cash"
},
"accountType": "Cash"
},
"Loans and Advances (Assets)": {
"isGroup": true
},
"Securities and Deposits": {
"Earnest Money": {}
},
"Stock Assets": {
"Stock In Hand": {
"accountType": "Stock"
},
"accountType": "Stock"
},
"Tax Assets": {
"isGroup": true
}
"Current Assets": {
"Accounts Receivable": {
"Debtors": {
"accountType": "Receivable"
}
},
"Fixed Assets": {
"Capital Equipments": {
"accountType": "Fixed Asset"
},
"Electronic Equipments": {
"accountType": "Fixed Asset"
},
"Furnitures and Fixtures": {
"accountType": "Fixed Asset"
},
"Office Equipments": {
"accountType": "Fixed Asset"
},
"Plants and Machineries": {
"accountType": "Fixed Asset"
},
"Buildings": {
"accountType": "Fixed Asset"
},
"Softwares": {
"accountType": "Fixed Asset"
},
"Accumulated Depreciation": {
"accountType": "Accumulated Depreciation"
}
},
"Investments": {
"Bank Accounts": {
"accountType": "Bank",
"isGroup": true
},
"Temporary Accounts": {
"Temporary Opening": {
"accountType": "Temporary"
}
"Cash In Hand": {
"Cash": {
"accountType": "Cash"
},
"accountType": "Cash"
},
"rootType": "Asset"
"Loans and Advances (Assets)": {
"isGroup": true
},
"Securities and Deposits": {
"Earnest Money": {}
},
"Stock Assets": {
"Stock In Hand": {
"accountType": "Stock"
},
"accountType": "Stock"
},
"Tax Assets": {
"isGroup": true
}
},
"Fixed Assets": {
"Capital Equipments": {
"accountType": "Fixed Asset"
},
"Electronic Equipments": {
"accountType": "Fixed Asset"
},
"Furnitures and Fixtures": {
"accountType": "Fixed Asset"
},
"Office Equipments": {
"accountType": "Fixed Asset"
},
"Plants and Machineries": {
"accountType": "Fixed Asset"
},
"Buildings": {
"accountType": "Fixed Asset"
},
"Softwares": {
"accountType": "Fixed Asset"
},
"Accumulated Depreciation": {
"accountType": "Accumulated Depreciation"
}
},
"Investments": {
"isGroup": true
},
"Temporary Accounts": {
"Temporary Opening": {
"accountType": "Temporary"
}
},
"rootType": "Asset"
},
"Expenses": {
"Direct Expenses": {
"Stock Expenses": {
"Cost of Goods Sold": {
"accountType": "Cost of Goods Sold"
},
"Expenses Included In Valuation": {
"accountType": "Expenses Included In Valuation"
},
"Stock Adjustment": {
"accountType": "Stock Adjustment"
}
}
"Direct Expenses": {
"Stock Expenses": {
"Cost of Goods Sold": {
"accountType": "Cost of Goods Sold"
},
"Expenses Included In Valuation": {
"accountType": "Expenses Included In Valuation"
},
"Stock Adjustment": {
"accountType": "Stock Adjustment"
}
}
},
"Indirect Expenses": {
"Administrative Expenses": {},
"Commission on Sales": {},
"Depreciation": {
"accountType": "Depreciation"
},
"Indirect Expenses": {
"Administrative Expenses": {},
"Commission on Sales": {},
"Depreciation": {
"accountType": "Depreciation"
},
"Entertainment Expenses": {},
"Freight and Forwarding Charges": {
"accountType": "Chargeable"
},
"Legal Expenses": {},
"Marketing Expenses": {
"accountType": "Chargeable"
},
"Miscellaneous Expenses": {
"accountType": "Chargeable"
},
"Office Maintenance Expenses": {},
"Office Rent": {},
"Postal Expenses": {},
"Print and Stationery": {},
"Round Off": {
"accountType": "Round Off"
},
"Salary": {},
"Sales Expenses": {},
"Telephone Expenses": {},
"Travel Expenses": {},
"Utility Expenses": {},
"Write Off": {},
"Exchange Gain/Loss": {},
"Gain/Loss on Asset Disposal": {}
"Entertainment Expenses": {},
"Freight and Forwarding Charges": {
"accountType": "Chargeable"
},
"rootType": "Expense"
"Legal Expenses": {},
"Marketing Expenses": {
"accountType": "Chargeable"
},
"Miscellaneous Expenses": {
"accountType": "Chargeable"
},
"Office Maintenance Expenses": {},
"Office Rent": {},
"Postal Expenses": {},
"Print and Stationery": {},
"Round Off": {
"accountType": "Round Off"
},
"Salary": {},
"Sales Expenses": {},
"Telephone Expenses": {},
"Travel Expenses": {},
"Utility Expenses": {},
"Write Off": {},
"Exchange Gain/Loss": {},
"Gain/Loss on Asset Disposal": {}
},
"rootType": "Expense"
},
"Income": {
"Direct Income": {
"Sales": {},
"Service": {}
},
"Indirect Income": {
"isGroup": true
},
"rootType": "Income"
"Direct Income": {
"Sales": {},
"Service": {}
},
"Indirect Income": {
"isGroup": true
},
"rootType": "Income"
},
"Source of Funds (Liabilities)": {
"Current Liabilities": {
"Accounts Payable": {
"Creditors": {
"accountType": "Payable"
},
"Payroll Payable": {}
},
"Stock Liabilities": {
"Stock Received But Not Billed": {
"accountType": "Stock Received But Not Billed"
}
},
"Duties and Taxes": {
"accountType": "Tax",
"isGroup": true
},
"Loans (Liabilities)": {
"Secured Loans": {},
"Unsecured Loans": {},
"Bank Overdraft Account": {}
}
"Current Liabilities": {
"Accounts Payable": {
"Creditors": {
"accountType": "Payable"
},
"Payroll Payable": {}
},
"rootType": "Liability"
"Stock Liabilities": {
"Stock Received But Not Billed": {
"accountType": "Stock Received But Not Billed"
}
},
"Duties and Taxes": {
"accountType": "Tax",
"isGroup": true
},
"Loans (Liabilities)": {
"Secured Loans": {},
"Unsecured Loans": {},
"Bank Overdraft Account": {}
}
},
"rootType": "Liability"
},
"Equity": {
"Capital Stock": {
"accountType": "Equity"
},
"Dividends Paid": {
"accountType": "Equity"
},
"Opening Balance Equity": {
"accountType": "Equity"
},
"Retained Earnings": {
"accountType": "Equity"
},
"rootType": "Equity"
"Capital Stock": {
"accountType": "Equity"
},
"Dividends Paid": {
"accountType": "Equity"
},
"Opening Balance Equity": {
"accountType": "Equity"
},
"Retained Earnings": {
"accountType": "Equity"
},
"rootType": "Equity"
}
}
}

View File

@ -29,7 +29,7 @@ located in `model/doc.ts`, all classes exported from `books/models` extend this.
- **Model**: the controller class that extends the `Doc` class, or the `Doc`
class itself (if a specific controller doesn't exist).
- **doc** (not `Doc`): instance of a Model, i.e. what has the data.
If you are confused, I understand.
## Initialization
@ -102,6 +102,7 @@ strings globally since this will be evaluated before the map is loaded.
The doc and db handlers have observers (instances of `Observable`) as
properties, these can be accessed using
- `fyo.db.observer`
- `fyo.doc.observer`
@ -109,4 +110,4 @@ The purpose of the observer is to trigger registered callbacks when some `doc`
operation or `db` operation takes place.
These are schema level observers i.e. they are registered like so:
`method:schemaName`. The callbacks receive args passed to the functions.
`method:schemaName`. The callbacks receive args passed to the functions.

View File

@ -15,7 +15,9 @@
"script:translate": "scripts/runner.sh scripts/generateTranslations.ts",
"script:profile": "scripts/profile.sh",
"test": "scripts/test.sh",
"lint": "eslint . --ext ts,vue"
"uitest": "node uitest/index.mjs | tap-spec",
"lint": "eslint . --ext ts,vue",
"format": "prettier --write ."
},
"dependencies": {
"@codemirror/autocomplete": "^6.4.2",
@ -44,7 +46,6 @@
"@types/electron-devtools-installer": "^2.2.0",
"@types/lodash": "^4.14.179",
"@types/luxon": "^2.3.1",
"@types/mocha": "^9.1.0",
"@types/node": "^17.0.23",
"@types/node-fetch": "^2.6.1",
"@types/tape": "^4.13.2",
@ -65,6 +66,7 @@
"eslint-plugin-vue": "^9.15.0",
"execa": "^7.1.1",
"fs-extra": "^11.1.1",
"playwright": "^1.35.1",
"postcss": "^8",
"prettier": "^2.4.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",

View File

@ -1,3 +1,3 @@
module.exports = {
plugins: [require('tailwindcss'), require('autoprefixer')]
plugins: [require('tailwindcss'), require('autoprefixer')],
};

View File

@ -17,7 +17,7 @@ There are a few types of schemas:
- **Subclass**: Schemas that have an `"extends"` field on them, the value of which
points to an Abstract schema.
- **Complete**: Schemas which are neither abstract nor stub.
For more detail on the meta structure of the schema check `books/schemas/types.ts`.
## Final Schema
@ -40,4 +40,4 @@ In all the schemas, the `"name"` field/column is the primary key. If it isn't
explicitly added, the schema builder will add it in.
The following schema fields will be implicitly translated by the frontend:
`"label"`, `"description"`, and `"placeholder"`, irrespective of nesting.
`"label"`, `"description"`, and `"placeholder"`, irrespective of nesting.

View File

@ -21,4 +21,4 @@
],
"keywordFields": ["name"],
"quickEditFields": ["details"]
}
}

View File

@ -21,4 +21,4 @@
}
],
"tableFields": ["account", "rate"]
}
}

View File

@ -2,4 +2,4 @@
"name": "Customer",
"label": "Customer",
"extends": "Party"
}
}

View File

@ -6,14 +6,14 @@ import {
cleanSchemas,
getAbstractCombinedSchemas,
getRegionalCombinedSchemas,
setSchemaNameOnFields
setSchemaNameOnFields,
} from '../index';
import { metaSchemas } from '../schemas';
import {
everyFieldExists,
getTestSchemaMap,
someFieldExists,
subtract
subtract,
} from './helpers';
const { appSchemaMap, regionalSchemaMap } = getTestSchemaMap();

View File

@ -1,6 +1,7 @@
<template>
<button
class="rounded-md flex justify-center items-center text-sm"
:disabled="disabled"
:class="_class"
v-bind="$attrs"
>

View File

@ -262,15 +262,15 @@ export default {
return;
}
if (label && this.suggestions.length === 0) {
if (this.suggestions.length === 0) {
this.triggerChange(label);
return;
}
if (
label &&
!this.suggestions.map(({ label }) => label).includes(label)
) {
const suggestion = this.suggestions.find((s) => s.label === label);
if (suggestion) {
this.setSuggestion(suggestion);
} else {
const suggestions = await this.getSuggestions(label);
this.setSuggestion(suggestions[0]);
}

View File

@ -14,6 +14,7 @@
"
>
<h6
data-testid="company-name"
class="
font-semibold
whitespace-nowrap
@ -115,6 +116,7 @@
</button>
<button
data-testid="change-db"
class="
flex
text-sm text-gray-600

View File

@ -26,6 +26,7 @@
<!-- New File (Blue Icon) -->
<div
data-testid="create-new-file"
class="px-4 h-row-largest flex flex-row items-center gap-4 p-2"
:class="creatingDemo ? '' : 'hover:bg-gray-50 cursor-pointer'"
@click="newDatabase"

View File

@ -60,6 +60,7 @@
<Button
type="primary"
class="w-24"
data-testid="submit-button"
:disabled="!areAllValuesFilled || loading"
@click="submit"
>{{ t`Submit` }}</Button

View File

@ -141,7 +141,7 @@ export async function getSavePath(name: string, extention: string) {
const canceled = response.canceled;
let filePath = response.filePath;
if (filePath && !filePath.endsWith(extention)) {
if (filePath && !filePath.endsWith(extention) && filePath !== ':memory:') {
filePath = `${filePath}.${extention}`;
}

View File

@ -64,4 +64,4 @@ test('import SalesInvoices', async (t) => {
);
});
closeTestFyo(fyo, __filename);
closeTestFyo(fyo, __filename);

View File

@ -6,7 +6,7 @@ import { getValueMapFromList } from 'utils';
import {
getTestDbPath,
getTestFyo,
getTestSetupWizardOptions
getTestSetupWizardOptions,
} from './helpers';
const dbPath = getTestDbPath();

88
uitest/index.mjs Normal file
View File

@ -0,0 +1,88 @@
import path from 'path';
import { _electron } from 'playwright';
import { fileURLToPath } from 'url';
import test from 'tape';
const dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(dirname, '..');
const appSourcePath = path.join(root, 'dist_electron', 'build', 'main.js');
(async function run() {
const electronApp = await _electron.launch({ args: [appSourcePath] });
const window = await electronApp.firstWindow();
window.setDefaultTimeout(60_000);
test('load app', async (t) => {
t.equal(await window.title(), 'Frappe Books', 'title matches');
await new Promise((r) => window.once('load', () => r()));
t.ok(true, 'window has loaded');
});
test('navigate to database selector', async (t) => {
/**
* When running on local, Frappe Books will open
* the last selected database.
*/
const changeDb = window.getByTestId('change-db');
const createNew = window.getByTestId('create-new-file');
const changeDbPromise = changeDb
.waitFor({ state: 'visible' })
.then(() => 'change-db');
const createNewPromise = createNew
.waitFor({ state: 'visible' })
.then(() => 'create-new-file');
const el = await Promise.race([changeDbPromise, createNewPromise]);
if (el === 'change-db') {
await changeDb.click();
await createNewPromise;
}
t.ok(await createNew.isVisible(), 'create new is visible');
});
test('fill setup form', async (t) => {
await electronApp.evaluate(({ dialog }, filePath) => {
dialog.showSaveDialog = () =>
Promise.resolve({ canceled: false, filePath });
}, ':memory:');
await window.getByTestId('create-new-file').click();
await window.getByTestId('submit-button').waitFor();
t.equal(
await window.getByTestId('submit-button').isDisabled(),
true,
'submit button is disabled before form fill'
);
await window.getByPlaceholder('Company Name').fill('Test Company');
await window.getByPlaceholder('John Doe').fill('Test Owner');
await window.getByPlaceholder('john@doe.com').fill('test@example.com');
await window.getByPlaceholder('Select Country').fill('India');
await window.getByPlaceholder('Select Country').blur();
await window.getByPlaceholder('Prime Bank').fill('Test Bank');
await window.getByPlaceholder('Prime Bank').blur();
t.equal(
await window.getByTestId('submit-button').isDisabled(),
false,
'submit button enabled after form fill'
);
});
test('create new instance', async (t) => {
await window.getByTestId('submit-button').click();
t.equal(
await window.getByTestId('company-name').innerText(),
'Test Company',
'new instance created, company name found in sidebar'
);
});
test('close app', async (t) => {
await electronApp.close();
t.ok(true, 'app closed without errors');
});
})();

View File

@ -2,12 +2,11 @@ import vue from '@vitejs/plugin-vue';
import path from 'path';
import { defineConfig } from 'vite';
/**
* This vite config file is used only for dev mode, i.e.
* to create a serve build modules of the source code
* which will be rendered by electron.
*
*
* For building the project, vite is used programmatically
* see build/scripts/build.mjs for this.
*/

View File

@ -651,11 +651,6 @@
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-2.3.1.tgz#e34763178b46232e4c5f079f1706e18692415519"
integrity sha512-nAPUltOT28fal2eDZz8yyzNhBjHw1NEymFBP7Q9iCShqpflWPybxHbD7pw/46jQmT+HXOy1QN5hNTms8MOTlOQ==
"@types/mocha@^9.1.0":
version "9.1.0"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5"
integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==
"@types/ms@*":
version "0.7.31"
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
@ -4484,6 +4479,18 @@ pkg-up@^3.1.0:
dependencies:
find-up "^3.0.0"
playwright-core@1.35.1:
version "1.35.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.1.tgz#52c1e6ffaa6a8c29de1a5bdf8cce0ce290ffb81d"
integrity sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==
playwright@^1.35.1:
version "1.35.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.35.1.tgz#f991d0c76ae517d4a0023d9428b09d19d5e87128"
integrity sha512-NbwBeGJLu5m7VGM0+xtlmLAH9VUfWwYOhUi/lSEDyGg46r1CA9RWlvoc5yywxR9AzQb0mOCm7bWtOXV7/w43ZA==
dependencies:
playwright-core "1.35.1"
plist@^3.0.4, plist@^3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3"