Skip to content

Commit

Permalink
Enhance step parameter tpyes
Browse files Browse the repository at this point in the history
* Parse primitive paramters
* Support custom parameter parser

Signed-off-by: BugDiver <[email protected]>
  • Loading branch information
BugDiver committed Jun 16, 2024
1 parent ea389a5 commit ced9df4
Show file tree
Hide file tree
Showing 20 changed files with 323 additions and 28 deletions.
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"files.associations": {
"*.spec": "gauge",
"*.cpt": "gauge"
},
"files.autoSave": "afterDelay",
"files.autoSaveDelay": 500,
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
1 change: 1 addition & 0 deletions biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"files": {
"ignore": [
"dist/**",
"coverage/**",
"node_modules",
"src/gen",
".vscode",
Expand Down
57 changes: 57 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,63 @@ String elementId = scenarioStore.get("element-id") as string;

```

### Custom Paramter Parsers

* By default gauge-ts tries to convert the spec parameter to primitives (number, boolean), for table parameters gauge-ts will convert paramters
to `Table` type. gauge-ts also provide a way to customize the paramter parsing.
If you need to have custom paramters in your step implementations create a paramter parser which should implements methods from `ParamterParser` interface.

Step definition:
```md

* step with a person parameter type "{\"name\": \"John\", \"age\": 40 }"

```

Step Implementation:
```javascript
import { Step } from 'gauge-ts';

export class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

public isAdult(): boolean {
return this.age >= 18;
}
}

export default class Implementation {
@Step
public async isAdult(person: Person)y {
assert.ok(person.isAdult())
}
}

```
Cusotm ParameterParser:
```javascript

import { Parameter, ParameterParser } from 'gauge-ts';
import { Person } from '@lib/Person';
export default class PersonParameterParser implements ParameterParser {

public canParse(paramter: Parameter): boolean {
return paramter.getValue().startsWith("{") && paramter.getValue().endsWith("}");
}

public parse(parameter: Parameter): Object {
const person = JSON.parse(parameter.getValue());
return new Person(person.name, person.age);
}
}

```

### Custom Screenshots

* By default gauge captures the display screen on failure if this feature has been enabled.
Expand Down
4 changes: 4 additions & 0 deletions e2e/specs/example.spec
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,7 @@ Here's a step that takes a table
|Snap |1 |
|GoCD |1 |
|Rhythm|0 |

## Custom Parameters in steps

* This step uses a custom parameter of type Person and value "{\"name\":\"John\",\"age\":30}"
12 changes: 12 additions & 0 deletions e2e/src/Person.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

public isAdult(): boolean {
return this.age >= 18;
}
}
14 changes: 14 additions & 0 deletions e2e/tests/PersonParameterParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Person } from "@lib/Person";
import type { Parameter, ParameterParser } from "gauge-ts";
export default class PersonParameterParser implements ParameterParser {
public canParse(paramter: Parameter): boolean {
return (
paramter.getValue().startsWith("{") && paramter.getValue().endsWith("}")
);
}

public parse(parameter: Parameter): Person {
const person = JSON.parse(parameter.getValue());
return new Person(person.name, person.age);
}
}
10 changes: 9 additions & 1 deletion e2e/tests/implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import * as assert from "node:assert";
import VowelCounter from "@lib/VowelCounter";
import { DataStoreFactory, Step, type Table } from "gauge-ts";

class Implementation {
import type { Person } from "@lib/Person";

export default class Implementation {
static vowelsCount = (word: string): number => {
const counter = DataStoreFactory.getSpecDataStore().get(
"counter",
Expand Down Expand Up @@ -31,4 +33,10 @@ class Implementation {
assert.equal(Implementation.vowelsCount(word), Number.parseInt(count));
}
}
@Step("This step uses a custom parameter of type Person and value <person>")
public async validatePerson(person: Person) {
assert.equal(person.name, "John");
assert.equal(person.age, 30);
assert.ok(person.isAdult());
}
}
9 changes: 7 additions & 2 deletions gauge-ts/src/RunnerServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import { StepNameProcessor } from "./processors/StepNameProcessor";
import { StepPositionsProcessor } from "./processors/StepPositionsProcessor";
import { StubImplementationCodeProcessor } from "./processors/StubImplementationCodeProcessor";
import { ValidationProcessor } from "./processors/ValidationProcessor";
import { ParameterParsingChain } from "./processors/params/ParameterParsingChain";
import { Util } from "./utils/Util";

type RpcError = {
Expand All @@ -92,8 +93,10 @@ export default class RunnerServer implements IRunnerServer {
private static stepPositionsProcessor: StepPositionsProcessor;
private static stubImplementationCodeProcessor: StubImplementationCodeProcessor;
private static validationProcessor: ValidationProcessor;
private static paramterParsingChain: ParameterParsingChain;

constructor(loader: StaticLoader) {
RunnerServer.paramterParsingChain = new ParameterParsingChain();
loader.loadImplementations();
RunnerServer.cacheFileProcessor = new CacheFileProcessor(loader);
RunnerServer.executionEndingProcessor = new ExecutionEndingProcessor();
Expand All @@ -109,7 +112,9 @@ export default class RunnerServer implements IRunnerServer {
new SpecExecutionStartingProcessor();
RunnerServer.stepExecutionEndingProcessor =
new StepExecutionEndingProcessor();
RunnerServer.stepExecutionProcessor = new StepExecutionProcessor();
RunnerServer.stepExecutionProcessor = new StepExecutionProcessor(
RunnerServer.paramterParsingChain,
);
RunnerServer.stepExecutionStartingProcessor =
new StepExecutionStartingProcessor();
RunnerServer.stepNameProcessor = new StepNameProcessor();
Expand Down Expand Up @@ -141,7 +146,7 @@ export default class RunnerServer implements IRunnerServer {
): void {
try {
DataStoreFactory.getSuiteDataStore().clear();
const loader = new ImplLoader();
const loader = new ImplLoader(RunnerServer.paramterParsingChain);

loader
.loadImplementations()
Expand Down
4 changes: 4 additions & 0 deletions gauge-ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Parameter } from "./gen/spec_pb";
import { ParameterParser } from "./processors/params/ParameterParser";
import { Gauge } from "./public/Gauge";
import { Operator } from "./public/Operator";
import { Table } from "./public/Table";
Expand Down Expand Up @@ -38,6 +40,8 @@ export {
AfterSpec,
AfterSuite,
CustomScreenshotWriter,
ParameterParser,
Parameter,
ExecutionContext,
Specification,
Scenario,
Expand Down
18 changes: 15 additions & 3 deletions gauge-ts/src/loaders/ImplLoader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import hookRegistry from "../models/HookRegistry";
import registry from "../models/StepRegistry";
import type { ParameterParser } from "../processors/params/ParameterParser";
import type { ParameterParsingChain } from "../processors/params/ParameterParsingChain";
import { Util } from "../utils/Util";

type ConstructorType = new () => Record<string, unknown>;
Expand All @@ -9,18 +11,28 @@ type ModuleType = {
};

export class ImplLoader {
private paramterParsignChain: ParameterParsingChain;

constructor(paramterParsignChain: ParameterParsingChain) {
this.paramterParsignChain = paramterParsignChain;
}

public async loadImplementations(): Promise<void> {
registry.clear();
hookRegistry.clear();
for (const file of Util.getListOfFiles()) {
try {
process.env.STEP_FILE_PATH = file;
const c = (await Util.importFile(file)) as ModuleType;

if (c.default && c.default.length === 0) {
const instance = new c.default();

ImplLoader.updateRegistry(file, instance);
if (Util.isCustomParameterParser(instance)) {
this.paramterParsignChain.addCustomParser(
instance as ParameterParser,
);
} else {
ImplLoader.updateRegistry(file, instance);
}
}
} catch (error) {
const err = error as Error;
Expand Down
26 changes: 8 additions & 18 deletions gauge-ts/src/processors/StepExecutionProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import type {
ExecuteStepRequest,
ExecutionStatusResponse,
} from "../gen/messages_pb";
import {
Parameter,
ProtoExecutionResult,
type ProtoTable,
} from "../gen/spec_pb";
import { ProtoExecutionResult } from "../gen/spec_pb";
import registry from "../models/StepRegistry";
import { Table } from "../public/Table";
import { Screenshot } from "../screenshot/Screenshot";
import { MessageStore } from "../stores/MessageStore";
import { ScreenshotStore } from "../stores/ScreenshotStore";
import type { CommonFunction } from "../utils/Util";
import { ExecutionProcessor } from "./ExecutionProcessor";
import type { ParameterParsingChain } from "./params/ParameterParsingChain";

export class StepExecutionProcessor extends ExecutionProcessor {
private parsingChain: ParameterParsingChain;
constructor(parameterParsingChain: ParameterParsingChain) {
super();
this.parsingChain = parameterParsingChain;
}
public async process(
req: ExecuteStepRequest,
): Promise<ExecutionStatusResponse> {
Expand All @@ -37,11 +38,7 @@ export class StepExecutionProcessor extends ExecutionProcessor {

result.setFailed(false);
const mi = registry.get(req.getParsedsteptext());
const params = req.getParametersList().map((item) => {
return this.isTable(item)
? Table.from(item.getTable() as ProtoTable)
: item.getValue();
});
const params = req.getParametersList().map(this.parsingChain.parse);

const method = mi.getMethod() as CommonFunction;

Expand Down Expand Up @@ -81,13 +78,6 @@ export class StepExecutionProcessor extends ExecutionProcessor {
return result;
}

private isTable(item: Parameter): boolean {
return (
item.getParametertype() === Parameter.ParameterType.TABLE ||
item.getParametertype() === Parameter.ParameterType.SPECIAL_TABLE
);
}

private executionError(message: string): ExecutionStatusResponse {
const result = new ProtoExecutionResult();

Expand Down
6 changes: 6 additions & 0 deletions gauge-ts/src/processors/params/ParameterParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { Parameter } from "../../gen/spec_pb";

export interface ParameterParser {
canParse(paramter: Parameter): boolean;
parse(parameter: Parameter): unknown;
}
26 changes: 26 additions & 0 deletions gauge-ts/src/processors/params/ParameterParsingChain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Parameter } from "../../gen/spec_pb";
import type { ParameterParser } from "./ParameterParser";
import { PrimitiveParser } from "./PrimitiveParser";
import { TableParameterParser } from "./TableParameterParser";

export class ParameterParsingChain {
private chain: Array<ParameterParser> = [];

public constructor() {
this.chain.push(new TableParameterParser());
this.chain.push(new PrimitiveParser());
}

public parse(parameter: Parameter): unknown {
for (const parser of this.chain) {
if (parser.canParse(parameter)) {
return parser.parse(parameter);
}
}
return parameter.getValue();
}

public addCustomParser(parser: ParameterParser): void {
this.chain.unshift(parser);
}
}
37 changes: 37 additions & 0 deletions gauge-ts/src/processors/params/PrimitiveParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { Parameter } from "../../gen/spec_pb";
import type { ParameterParser } from "./ParameterParser";

type ConvertFunction = (value: string) => unknown | undefined;

export class PrimitiveParser implements ParameterParser {
public readonly converters: ConvertFunction[] = [];

constructor() {
this.converters.push(this.convertToNumber);
this.converters.push(this.convertToBoolean);
}

public canParse(parameter: Parameter): boolean {
return true;
}

public parse(parameter: Parameter): unknown {
const paramValue = parameter.getValue();
for (const converter of this.converters) {
const v = converter(paramValue);
if (v !== undefined) {
return v;
}
}
return paramValue;
}

private convertToNumber(value: string): number | undefined {
const num = Number(value);
return Number.isNaN(num) ? undefined : num;
}

private convertToBoolean(value: string): boolean | undefined {
return value === "true" || value === "false" ? value === "true" : undefined;
}
}
16 changes: 16 additions & 0 deletions gauge-ts/src/processors/params/TableParameterParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Parameter, type ProtoTable } from "../../gen/spec_pb";
import { Table } from "../../public/Table";
import type { ParameterParser } from "./ParameterParser";

export class TableParameterParser implements ParameterParser {
public canParse(parameter: Parameter): boolean {
return (
parameter.getParametertype() === Parameter.ParameterType.TABLE ||
parameter.getParametertype() === Parameter.ParameterType.SPECIAL_TABLE
);
}

public parse(parameter: Parameter): Table {
return Table.from(parameter.getTable() as ProtoTable);
}
}
Loading

0 comments on commit ced9df4

Please sign in to comment.