Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom parameters support + Hook conflict fix #182

Merged
merged 4 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 Parameter Parsers

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

```
Custom 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(parameter: Parameter): boolean {
return (
parameter.getValue().startsWith("{") && parameter.getValue().endsWith("}")
);
}

public parse(parameter: Parameter): Person {
const person = JSON.parse(parameter.getValue());
return new Person(person.name, person.age);
}
}
19 changes: 15 additions & 4 deletions 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 All @@ -19,16 +21,25 @@ class Implementation {
}

@Step("The word <gauge> has <3> vowels.")
public async checkWord(word: string, count: string) {
assert.equal(Implementation.vowelsCount(word), Number.parseInt(count));
public async checkWord(word: string, count: number) {
assert.strictEqual(Implementation.vowelsCount(word), count);
}

@Step("Almost all words have vowels <table>")
public async checkTableOfWords(table: Table) {
for (const row of table.getTableRows()) {
const word = row.getCell("Word");
const count = row.getCell("Vowel Count");
assert.equal(Implementation.vowelsCount(word), Number.parseInt(count));
assert.strictEqual(
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.strictEqual(person.name, "John");
assert.strictEqual(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 parameterParsingChain: ParameterParsingChain;

constructor(loader: StaticLoader) {
RunnerServer.parameterParsingChain = 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.parameterParsingChain,
);
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.parameterParsingChain);

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 parameterParsignChain: ParameterParsingChain;
BugDiver marked this conversation as resolved.
Show resolved Hide resolved

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

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.parameterParsignChain.addCustomParser(
BugDiver marked this conversation as resolved.
Show resolved Hide resolved
instance as ParameterParser,
);
} else {
ImplLoader.updateRegistry(file, instance);
}
}
} catch (error) {
const err = error as Error;
Expand Down
4 changes: 3 additions & 1 deletion gauge-ts/src/models/HookRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ export class HookRegistry {
): void {
for (const hookMethods of this._hooks.values()) {
for (const hookMethod of hookMethods) {
hookMethod.setInstance(instance);
if (hookMethod.getFilePath() === file) {
hookMethod.setInstance(instance);
}
}
}
}
Expand Down
28 changes: 10 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,9 @@ 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((p) => this.parsingChain.parse(p));

const method = mi.getMethod() as CommonFunction;

Expand Down Expand Up @@ -81,13 +80,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;
}
}
Loading
Loading