Skip to content

Commit

Permalink
Merge pull request #10 from savanesoff/unit-tests
Browse files Browse the repository at this point in the history
API changes, UTs, Readme
  • Loading branch information
savanesoff committed Jul 14, 2023
2 parents c2ce956 + 766827c commit 62c06d7
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 72 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# devtools-chopper

## 1.0.0

### Major Changes

- Changes props names and inclused unit tests. Readme includes advanced options.

## 0.2.2

### Patch Changes
Expand Down
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[![by Protosus](https://raw.githubusercontent.com/savanesoff/protosus/main/public/icons/by-protosus.svg)](https://github.com/savanesoff/devtools-chopper)

# devtools-chopper

[![Github](https://badgen.net/badge/Protosus/devtools-chopper?color=purple&icon=github)](https://github.com/savanesoff/devtools-chopper)
Expand Down Expand Up @@ -58,6 +58,52 @@ chopper.$log('Message');
chopper.$pin.log('Message');
```

### API

Standard log API available:

- `$debug()`
- `$log()`
- `$info()`
- `$warn()`
- `$error()`

To pin a message that is not los in scrolling jungle, use `$pin.<type>`, the message will be printed as a pinned entry as well as in the scrolling set of messages. Its a great way to track logs without searching for it.
However, using standard console of devtools, you can simply enter a filter parameter to achieve the very same effect.

Each takes unlimited number of arguments, just like a typical `console` method would.

```TS
// ...
chopper.$log('arg1', 'arg3', 'arg3', ...);
```

Messages will not be rendered if `logLevel` is set to filter out a message log type.

Ex: If `logLevel` is set to `none`, no messages will be printed.

Here is a map of `logLevel` gates:

```JSON
{
"verbose": ["debug", "log", "info", "warn", "error"],
"info": ["info", "warn", "error"],
"warn": ["warn", "error"],
"error": ["error"],
"none": [],
}

```

<details>
<summary> Advanced APIs </summary>
If you want to modify various parameters of a `chopper` instance, the following APIs are available for use:

- `setLogLevel( level Levels )`: changes current log level
- `console(data: unknown[], type: ConsoleType, info: EntryInfo)`: Override method that prints to console. Ex: if you'd like it to print only raw data without prettifying it with styles and other info.
- `renderEntry(parent: HTMLElement, data: unknown[], type: ConsoleType, info: EntryInfo)`: override how the chopper renders log entry element.
</details>

## Constructor

The Overdrag class constructor accepts an object with the following properties:
Expand Down Expand Up @@ -94,6 +140,9 @@ Optional:
- **`stack`** (default: `false`): If true, an `Overdrag` parent element that has a recursively embedded `Overdrag` elements as a child will retain `over` state while the child is active. Else, the parent element will be set to `out` state (inactive)
</details>

> **NOTE**
> Multiple instances can be spawned. Each will position itself randomly unless you specify its element style or provide your own element.
## Extending Class

Use `devtools-chopper` functionality to track any class instances logs, so you can monitor its activity:
Expand All @@ -116,6 +165,14 @@ class MyClass extends Chopper {

You can control many different aspects of the instance by referring to its `overdrag` extension [documentation](https://www.npmjs.com/package/devtools-chopper)

### Log Entry

Each log entry includes the following info:

- Type of log and time of entry `<log type>@<time>` ex: `debug@12345555353`
- Log call origin file path and position: `<file path>:<line>:<column>`
- Log message as string

### In Brief:

`devtools-chopper` includes the following functionalities and interactions:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "devtools-chopper",
"private": false,
"version": "0.2.2",
"version": "1.0.0",
"license": "MIT",
"description": "Chopper is a Javascript log display devtool which allows you to monitor application logs in DOM",
"bugs": {
Expand Down
35 changes: 19 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type EntryInfo = {
// Define props interface for Chopper class
interface ChopperProps extends Partial<ControlProps> {
name: string;
level?: Levels;
logLevel?: Levels;
styles?: Styles;
console?: boolean;
}
Expand All @@ -30,7 +30,7 @@ const TIME_FORMAT: Intl.DateTimeFormatOptions = {

// Define Chopper class
export default class Chopper extends Overdrag {
protected level: Levels;
protected logLevel: Levels;
protected readonly name: string;
protected readonly styles: Styles;
protected readonly headerElement: HTMLElement;
Expand Down Expand Up @@ -70,7 +70,7 @@ export default class Chopper extends Overdrag {

constructor({
console = true,
level = "verbose",
logLevel = "verbose",
name,
styles,
element,
Expand All @@ -86,6 +86,7 @@ export default class Chopper extends Overdrag {
if (!element) {
element = document.createElement("div");
document.body.appendChild(element);
// position randomly on the screen
element.style.left = `${Math.floor(
Math.random() * (window.innerWidth - parseInt(CLASSES.container.width))
)}px`;
Expand All @@ -104,7 +105,7 @@ export default class Chopper extends Overdrag {
controlsThreshold,
clickDetectionThreshold,
});
this.level = level;
this.logLevel = logLevel;
this.name = name;
this.styles = compileStyles({ ...CONSOLE_STYLE, ...styles });
this.printToConsole = console;
Expand Down Expand Up @@ -147,7 +148,7 @@ export default class Chopper extends Overdrag {
parent: this.titleElement,
type: "div",
classNames: ["chopper-level"],
text: this.level,
text: this.logLevel,
});

this.statusElement = this.createInternalElement({
Expand Down Expand Up @@ -183,9 +184,9 @@ export default class Chopper extends Overdrag {
// Make logLevelElement clickable that changes log level
this.logLevelElement.addEventListener("click", () => {
const levels = Object.keys(this.gate) as Levels[];
const index = levels.indexOf(this.level);
this.level = levels[(index + 1) % levels.length];
this.logLevelElement.textContent = this.level;
const index = levels.indexOf(this.logLevel);
this.logLevel = levels[(index + 1) % levels.length];
this.logLevelElement.textContent = this.logLevel;

this.setLevelButtonColor();
});
Expand All @@ -196,15 +197,15 @@ export default class Chopper extends Overdrag {
private setLevelButtonColor() {
// Change the background color of the logLevelElement based on the log level
this.logLevelElement.style.backgroundColor =
LEVEL_COLORS[this.level].backgroundColor || "transparent";
LEVEL_COLORS[this.logLevel].backgroundColor || "transparent";

// Change the color of the logLevelElement based on the log level
this.logLevelElement.style.color =
LEVEL_COLORS[this.level].color || "black";
LEVEL_COLORS[this.logLevel].color || "black";

// Change outline color of the logLevelElement based on the log level
this.logLevelElement.style.outlineColor =
LEVEL_COLORS[this.level].color || "black";
LEVEL_COLORS[this.logLevel].color || "black";
}

private createInternalElement({
Expand Down Expand Up @@ -233,9 +234,11 @@ export default class Chopper extends Overdrag {

// Set log level
setLogLevel(level: Levels) {
this.level = level;
this.logLevel = level;
}

readonly getLogLevel = () => this.logLevel;

private getLogInfo(stackIndex: number): EntryInfo {
const time = new Date().toLocaleTimeString("en-US", TIME_FORMAT);
const line = (new Error().stack?.split("\n") || [])[stackIndex]?.replace(
Expand All @@ -257,7 +260,7 @@ export default class Chopper extends Overdrag {

private renderOutput(data: unknown[], type: ConsoleType) {
// Filter out the types that are not allowed
if (!this.gate[this.level].includes(type)) return;
if (!this.gate[this.logLevel].includes(type)) return;

const info = this.getLogInfo(4);
// TODO: Detect data types and JSON.parse the objects with indentation
Expand Down Expand Up @@ -328,7 +331,7 @@ export default class Chopper extends Overdrag {
parent: entry,
type: "pre",
classNames: ["chopper-data"],
text: "> " + data.join("\n> "),
text: data.join("\n"),
});

return entry;
Expand All @@ -338,14 +341,14 @@ export default class Chopper extends Overdrag {
this.pinnedOutputElement.innerHTML = "";
this.pinned.forEach(({ data, info }, type) => {
// Filter out the types that are not allowed
if (!this.gate[this.level].includes(type)) return;
if (!this.gate[this.logLevel].includes(type)) return;
this.renderEntry(this.pinnedOutputElement, data, type, info);
});
}

private readonly pin = (level: ConsoleType, data: unknown[]) => {
const info = this.getLogInfo(4);
this.console(data, level, info);
this.renderOutput(data, level);
this.pinned.set(level, { data, info });
this.renderPinned();
};
Expand Down
110 changes: 56 additions & 54 deletions tests/constructor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,72 @@
import { screen } from "@testing-library/dom";
import "vitest-dom/extend-expect";
import Chopper from "./../src";
// eslint-disable-next-line @typescript-eslint/no-var-requires
function isNullOrUndefined(value: unknown): boolean {
return value === null || value === undefined;
}
import { ControlProps } from "overdrag";
import { getRandomValue } from "./setup";

/**
* Mock HTMLElement.offsetParent as it is not supported in JEST DOM
*/
Object.defineProperty(HTMLElement.prototype, "offsetParent", {
get() {
// eslint-disable-next-line @typescript-eslint/no-this-alias
let element = this;
while (
!isNullOrUndefined(element) &&
(isNullOrUndefined(element.style) ||
isNullOrUndefined(element.style.display) ||
element.style.display.toLowerCase() !== "none")
) {
element = element.parentNode;
}
afterEach(() => {
// Reset mock function calls
vi.clearAllMocks();
});

if (!isNullOrUndefined(element)) {
return null;
}
describe("Constructor", () => {
it(`should be defined`, () => {
expect(Chopper).toBeDefined();
});

if (
!isNullOrUndefined(this.style) &&
!isNullOrUndefined(this.style.position) &&
this.style.position.toLowerCase() === "fixed"
) {
return null;
}
it("should render chopper", () => {
new Chopper({ name: "Super name" });
expect(screen.queryByText("Super name")).toBeInTheDocument();
});

if (
this.tagName.toLowerCase() === "html" ||
this.tagName.toLowerCase() === "body"
) {
return null;
}
it('should have log level "verbose" by default', () => {
const chopper = new Chopper({ name: "Super name" });
expect(chopper.getLogLevel()).toBe("verbose");
});

return this.parentNode;
},
it("Should initialize super with parameters", () => {
const element = document.createElement("div");
globalThis.document.body.appendChild(element);
const overdragProps: Partial<ControlProps> = {
element,
maxContentHeight: getRandomValue(),
maxContentWidth: getRandomValue(),
minContentHeight: getRandomValue(),
minContentWidth: getRandomValue(),
snapThreshold: getRandomValue(),
controlsThreshold: getRandomValue(),
clickDetectionThreshold: getRandomValue(),
};
const chopper = new Chopper({ name: "Super name", ...overdragProps });
expect(chopper.element).toBe(overdragProps.element);
expect(chopper.maxContentHeight).toBe(overdragProps.maxContentHeight);
expect(chopper.maxContentWidth).toBe(overdragProps.maxContentWidth);
expect(chopper.minContentHeight).toBe(overdragProps.minContentHeight);
expect(chopper.minContentWidth).toBe(overdragProps.minContentWidth);
expect(chopper.snapThreshold).toBe(overdragProps.snapThreshold);
expect(chopper.controlsThreshold).toBe(overdragProps.controlsThreshold);
expect(chopper.clickDetectionThreshold).toBe(
overdragProps.clickDetectionThreshold
);
});
});

export function getRandomValue(min = 0, max = 50) {
return min + Math.round(Math.random() * (max - min));
}

export function getRandomPixelValue(min = 0, max = 50) {
return `${getRandomValue(min, max)}px`;
}

describe("Constructor", () => {
afterEach(() => {
// Reset mock function calls
vi.clearAllMocks();
describe("APIs", () => {
let chopper: Chopper;
beforeEach(() => {
chopper = new Chopper({ name: "Super name" });
});

it(`Should be defined`, () => {
expect(Chopper).toBeDefined();
describe("getLogLevel", () => {
it("should return log level", () => {
expect(chopper.getLogLevel()).toBe("verbose");
});
});

it("Should render chopper", () => {
new Chopper({ name: "Super name" });
expect(screen.getByText("Super name")).toBeInTheDocument();
describe("setLogLevel", () => {
it("should set log level", () => {
chopper.setLogLevel("error");
expect(chopper.getLogLevel()).toBe("error");
});
});
});
Loading

0 comments on commit 62c06d7

Please sign in to comment.