Skip to content
This repository has been archived by the owner on Nov 20, 2020. It is now read-only.

Commit

Permalink
Merge pull request #10 from Siemienik/issue/3-tests
Browse files Browse the repository at this point in the history
Integration tests PART 1
  • Loading branch information
Siemienik authored Mar 26, 2020
2 parents 4498835 + c5c374c commit 339cc41
Show file tree
Hide file tree
Showing 45 changed files with 411 additions and 124 deletions.
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
/.nyc_output/
/.idea/


/src/example.ts
*.xlsx
test-output.xlsx
~$*.xlsx
83 changes: 50 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@

# WIP current version of lib is under developing

# Introduction

This library makes generating xlsx files (Excel) easly.

It consumes template which is common Excel file, then add yours data (called ViewModel). Blend it and done, as result you'll get pretty Excel.

# Getting Started:

1. install package
Expand All @@ -11,50 +17,61 @@
npm i xlsx-renderer --save
```

`npm install`
2. TODO add some example here (https://github.com/Siemienik/xlsx-renderer/issues/6)

## In Cell Commands:

1. `## varToDisplayInThisCell`
2. `#! FINISH` - finish processing current worksheet
2. `#! FINISH booleanVar` - if true it finishes processing current template worksheet, if false it add again this worksheet to output
3. `#! END_ROW`
4. `#! DELETE varName`
5. `#! HYPERLINK labelVar urlVar`
6. `#! WS_NAME nameVar` set worksheet name
7. `#! FOR_EACH item collection` (to write item property `## item.property`),
8. `#! CONTINUE item` item is set to the next collection item.
9. `#! END_LOOP item`
10. `#! AVERAGE item` write average formula of all items from previous for-each, it has to be placed after the for-each was finished.
11. `#! SUM item` similar to average
12. `#! DUMP_COLS arrayVar` write to next columns all array items (1 item = 1 column)


## Sample code: (possible to be outdated yet - todo)
## Sample code:

```javascript
import Renderer from './xls-renderer/Renderer'
import {Workbook} from 'exceljs'

//*
import DebugCellTemplatePool from "./xls-renderer-debug/CellTemplateDebugPool";
const renderer = new Renderer(new DebugCellTemplatePool());
/*/
import CellTemplatePool from "./xls-renderer/CellTemplatePool";
const renderer = new Renderer(new CellTemplatePool());
//*/


const viewModel = new MyAwesomeReportVm(); //or something else

(async () => {
const result = await renderer.render(async () => {
const template = new Workbook();
return await template.xlsx.readFile('./my-awesome-raport-template.xlsx');
}, viewModel);
const result = await renderer.renderFromFile('./my-awesome-raport-template.xlsx', viewModel);

await result.xlsx.writeFile('./my-awesome-raport.xlsx');
})();
```

## More examples:

for more example I invite to tests data: [click here and check `Renderer` folders](./tests/integration/data)

# Documentation:

## Cells:

| Category | Name | Order | match rule | Description | More info |
|----------|-----:|-------|--------|-------------|:---------|
| - | [BaseCell](./src/cell/BaseCell.ts) | n/o | n/o | All Cell\`s definition classes extend it. | **abstract** |
| Content | [NormalCell](./src/cell/NormalCell.ts) | 1 | not started by `##` or `#!` | This one copy all styles, width, properties and value form template. | **default** |
| Content | [VariableCell](./src/cell/VariableCell.ts) | 3 | `## pathToVariable ` | Write variable from `ViewModel`. <br/> Paths to object's property or array item are allowed.<br/> When asking about undefined variable it returns empty string. | **Paths examples:** <br/> `simplePath` <br/> `someObject.property` <br/> `array.0.field` <br/> `items.1.path.to.object.prop`|
| Content | [HyperlinkCell](./src/cell/HyperlinkCell.ts) | 5 | `#! HYPERLINK pathToLabel pathToTarget` | Create a hyperlink. | *Paths resolve exactly same as VariableCell* |
| Content | **TODO: describe it!** [FormulaCell](./src/cell/FormulaCell.ts) | | | | |
| Navigation | [EndRowCell](./src/cell/EndRowCell.ts) | 2 | `#! END_ROW` | Go to the beginning of next row | |
| Worksheet<br/>Navigation<br/>Loop | [FinishCell](./src/cell/FinishCell.ts) | 7 | `#! FINISH conditionPath` | Finish rendering for current worksheet and: <br/> 1) go to next worksheet if `conditionPath===true`<br/> 2) repeat this template worksheet again (`conditionPath === false`) - looping through worksheets <br/> 3) finished whole rendering when this worksheet is the last one. | **Examples:**<br/> `#! FINISHED ` or `#! FINISHED itemFromLoop.__iterated` |
| Worksheet | [WsNameCell](./src/cell/WsNameCell.ts) | 13 | `#! WS_NAME pathToVariable` | Set worksheet's name. | **Examples:** <br/> `#! WS_NAME worksheetName` <br/> `#! WS_NAME item.title` <br/> `#! WS_NAME translatedNames.0` |
| Loop | **TODO: describe it!** [DumpColsCell](./src/cell/DumpColsCell.ts) | | | | |
| Loop | **TODO: describe it! test done: simple; tests todo: nested, with formula, stripped, through worksheets,** [ForEachCell](./src/cell/ForEachCell.ts) | | | | |
| Loop | **TODO: describe it!** [ContinueCell](./src/cell/ContinueCell.ts) | | | | |
| Loop | **TODO: describe it!** [EndLoopCell](./src/cell/EndLoopCell.ts) | | | | |
| Aggregation | **TODO: describe it!** [AverageCell](./src/cell/AverageCell.ts) | | | | |
| Aggregation| **TODO: describe it!** [SumCell](./src/cell/SumCell.ts) | | | | |
| View Model | **TODO: test - nested loop** [DeleteCell](./src/cell/DeleteCell.ts) | 14 | `#! DELETE pathToVariable` | Delete variable, useful for nested loops.| |


## Commands [PREVIOUS VERSION]:

1. `#! END_ROW`
4. `#! DELETE varName`
5. `#! HYPERLINK labelVar urlVar`
6. `#! WS_NAME nameVar` set worksheet name
7. `#! FOR_EACH item collection` (to write item property `## item.property`),
8. `#! CONTINUE item` item is set to the next collection item.
9. `#! END_LOOP item`
10. `#! AVERAGE item` write average formula of all items from previous for-each, it has to be placed after the for-each was finished.
11. `#! SUM item` similar to average
12. `#! DUMP_COLS arrayVar` write to next columns all array items (1 item = 1 column)


[LICENSE](LICENSE)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"postversion": "git push && git push --tags"
},
"devDependencies": {
"@types/node": "^12.12.6",
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.6",
"chai": "^4.2.0",
Expand Down
12 changes: 10 additions & 2 deletions src/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { Scope } from './Scope';
import { CellTemplatePool } from './CellTemplatePool';

export class Renderer {
constructor(private cellTemplatePool: CellTemplatePool = new CellTemplatePool()) {
}
constructor(private cellTemplatePool: CellTemplatePool = new CellTemplatePool()) {}

public async render(templateFactory: () => Promise<Workbook>, vm: any): Promise<Workbook> {
const template = await templateFactory();
Expand All @@ -19,4 +18,13 @@ export class Renderer {

return output;
}

public async renderFromFile(templatePath: string, viewModel: any): Promise<Workbook> {
const result = await this.render(async () => {
const template = new Workbook();
return await template.xlsx.readFile(templatePath);
}, viewModel);

return await result;
}
}
3 changes: 1 addition & 2 deletions src/Scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ export class Scope {

private finished: boolean = false;

constructor(public template: Workbook, public output: Workbook, public vm: ViewModel) {
}
constructor(public template: Workbook, public output: Workbook, public vm: ViewModel) {}

public getCurrentTemplateValue(): CellValue {
return this.getCurrentTemplateCell().value;
Expand Down
20 changes: 15 additions & 5 deletions src/cell/AverageCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@ import { Scope } from '../Scope';
import { Cell, CellFormulaValue, ValueType } from 'exceljs';
/* tslint:disable:variable-name */
export class AverageCell extends BaseCell {

public static match(cell: Cell): boolean {
return cell && cell.type === ValueType.String && typeof cell.value === 'string' && cell.value.substring(0, 10) === '#! AVERAGE';
return (
cell &&
cell.type === ValueType.String &&
typeof cell.value === 'string' &&
cell.value.substring(0, 10) === '#! AVERAGE'
);
}

protected static getTargetParam(scope: Scope): string {
return scope.getCurrentTemplateValue()?.toString().split(' ')[2] || '';
return (
scope
.getCurrentTemplateValue()
?.toString()
.split(' ')[2] || ''
);
}
public apply(scope: Scope): AverageCell {
super.apply(scope);
Expand All @@ -19,7 +28,8 @@ export class AverageCell extends BaseCell {
const __endOutput = scope.vm[target] && scope.vm[target].__endOutput;

if (__startOutput && __endOutput) {
const start = scope.output.worksheets[scope.outputCell.ws].getCell(__startOutput, scope.outputCell.c).address; // todo refactoring
const start = scope.output.worksheets[scope.outputCell.ws].getCell(__startOutput, scope.outputCell.c)
.address; // todo refactoring
const end = scope.output.worksheets[scope.outputCell.ws].getCell(__endOutput, scope.outputCell.c).address; // todo refactoring

scope.setCurrentOutputValue({ formula: `average(${start}:${end})` } as CellFormulaValue);
Expand All @@ -29,4 +39,4 @@ export class AverageCell extends BaseCell {

return this;
}
}
}
4 changes: 3 additions & 1 deletion src/cell/BaseCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { Scope } from '../Scope';

export declare type CellType = typeof BaseCell;

export class BaseCell {
export /* abstract */ class BaseCell {
// can't be abstract :(
/**
* check if this commend can parse `value`
*/
Expand All @@ -13,6 +14,7 @@ export class BaseCell {
}

public BaseCell() {
// can't be marked by abstract keyword, so it throw type error.
throw new TypeError(`Cannot construct ${BaseCell.name} instances directly. It's abstract.`);
}

Expand Down
8 changes: 6 additions & 2 deletions src/cell/ContinueCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ import { Scope } from '../Scope';

export class ContinueCell extends ForEachCell {
public static match(cell: Cell): boolean {
return cell && cell.type === ValueType.String && typeof cell.value === 'string' && cell.value.substring(0, 11) === '#! CONTINUE';
return (
cell &&
cell.type === ValueType.String &&
typeof cell.value === 'string' &&
cell.value.substring(0, 11) === '#! CONTINUE'
);
}

public getSourceParam(scope: Scope): string {
const target = ForEachCell.getTargetParam(scope);

return scope.vm[target] && scope.vm[target].__from;
}

}
19 changes: 14 additions & 5 deletions src/cell/DeleteCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ import { Scope } from '../Scope';

export class DeleteCell extends BaseCell {
public static match(cell: Cell): boolean {
return cell && cell.type === ValueType.String && typeof cell.value === 'string' && cell.value.substring(0, 9) === '#! DELETE';
return (
cell &&
cell.type === ValueType.String &&
typeof cell.value === 'string' &&
cell.value.substring(0, 9) === '#! DELETE'
);
}

public apply(scope: Scope): DeleteCell {
super.apply(scope);

const target = scope.getCurrentTemplateValue()?.toString().split(' ')[2]; // todo make some function for scope.getCurrentTemplateValue()?.toString().split(' ') ;
const target = scope
.getCurrentTemplateValue()
?.toString()
.split(' ')[2]; // todo make some function for scope.getCurrentTemplateValue()?.toString().split(' ') ;

if (target === undefined) { return this; } // it's ok here
if (target === undefined) {
return this;
} // it's ok here

scope.vm[target] = undefined;

Expand All @@ -21,5 +31,4 @@ export class DeleteCell extends BaseCell {

return this;
}

}
}
15 changes: 12 additions & 3 deletions src/cell/DumpColsCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@ import { Scope } from '../Scope';

export class DumpColsCell extends BaseCell {
public static match(cell: Cell): boolean {
return cell && cell.type === ValueType.String && typeof cell.value === 'string' && cell.value.substring(0, 12) === '#! DUMP_COLS';
return (
cell &&
cell.type === ValueType.String &&
typeof cell.value === 'string' &&
cell.value.substring(0, 12) === '#! DUMP_COLS'
);
}

public apply(scope: Scope): DumpColsCell {
super.apply(scope);

const path = scope.getCurrentTemplateValue()?.toString().substring(13).split('.') || '';
const path =
scope
.getCurrentTemplateValue()
?.toString()
.substring(13)
.split('.') || '';
const cols = Array.from(path).reduce((p, c) => p[c] || [], scope.vm);

scope.setCurrentOutputValue(null);
Expand All @@ -25,5 +35,4 @@ export class DumpColsCell extends BaseCell {

return this;
}

}
39 changes: 27 additions & 12 deletions src/cell/ForEachCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,47 @@ import { Cell, ValueType } from 'exceljs';
*/
export class ForEachCell extends BaseCell {
public static match(cell: Cell): boolean {
return cell && cell.type === ValueType.String && typeof cell.value === 'string' && cell.value.substring(0, 11) === '#! FOR_EACH';
return (
cell &&
cell.type === ValueType.String &&
typeof cell.value === 'string' &&
cell.value.substring(0, 11) === '#! FOR_EACH'
);
}

protected static getTargetParam(scope: Scope): string {
return scope.getCurrentTemplateValue()?.toString().split(' ')[2] || '';
return (
scope
.getCurrentTemplateValue()
?.toString()
.split(' ')[2] || ''
);
}


public apply(scope: Scope): ForEachCell {
const target = ForEachCell.getTargetParam(scope);
const __from = this.getSourceParam(scope);

// todo refactoring
const __index = (scope.vm[target] && scope.vm[target].__index || 0) + 1;
const __index = ((scope.vm[target] && scope.vm[target].__index) || 0) + 1;
if (__index === 1) {
super.apply(scope);
}

const __start = scope.vm[target] && scope.vm[target].__start || scope.templateCell;
const __startOutput = scope.vm[target] && scope.vm[target].__startOutput || scope.outputCell.r + 1;
const __start = (scope.vm[target] && scope.vm[target].__start) || scope.templateCell;
const __startOutput = (scope.vm[target] && scope.vm[target].__startOutput) || scope.outputCell.r + 1;
const __end = scope.vm[target] && scope.vm[target].__end;
const __last = typeof __from.split('.').reduce((p, c) => p[c] || {}, scope.vm)[__index] === 'undefined';
let __endOutput = scope.vm[target] && scope.vm[target].__endOutput;
let __insetRows = scope.vm[target] && scope.vm[target].__insetRows || false;
let __insetRows = (scope.vm[target] && scope.vm[target].__insetRows) || false;

let next = __from.split('.').reduce((p, c) => p[c] || {}, scope.vm)[__index - 1];

let __iterated = scope.vm[target] && scope.vm[target].__iterated;
__iterated = typeof __iterated !== 'undefined' && __iterated;

scope.setCurrentOutputValue(null);

if (!__iterated && !next) {
__iterated = true;
scope.freezeOutput();
Expand All @@ -62,7 +71,8 @@ export class ForEachCell extends BaseCell {
__insetRows = false;
if (!scope.isFrozen()) {
for (let i = __end.r; i > __start.r; i--) {
scope.output.worksheets[scope.outputCell.ws].spliceRows( // todo refactoring
scope.output.worksheets[scope.outputCell.ws].spliceRows(
// todo refactoring
scope.outputCell.r + 1,
0,
[],
Expand All @@ -87,13 +97,18 @@ export class ForEachCell extends BaseCell {
__insetRows,
__startOutput,
__endOutput,
__last
__last,
});

return this;
}

protected getSourceParam(scope: Scope): string {
return scope.getCurrentTemplateValue()?.toString().split(' ')[3] || '';
return (
scope
.getCurrentTemplateValue()
?.toString()
.split(' ')[3] || ''
);
}
}
Loading

0 comments on commit 339cc41

Please sign in to comment.