From f475742cbf1ea6b25fefc054d345fabc94f3652d Mon Sep 17 00:00:00 2001
From: Ladd Hoffman
Date: Sun, 29 Jan 2023 04:38:28 -0600
Subject: [PATCH] Minor refactoring
---
forum-network/.eslintrc.js | 14 +++
forum-network/src/classes/exchange.js | 0
forum-network/src/classes/forum-view.js | 8 +-
forum-network/src/classes/forum.js | 90 ++++++++------
forum-network/src/classes/list.js | 9 ++
forum-network/src/classes/question.js | 0
forum-network/src/classes/scene.js | 39 +++++--
.../src/classes/{graph.js => wdag.js} | 50 ++++----
forum-network/src/index.css | 6 +
forum-network/src/index.html | 16 +--
...ailability.html => availability.test.html} | 0
.../src/tests/{basic.html => basic.test.html} | 0
.../{debounce.html => debounce.test.html} | 0
.../{flowchart.html => flowchart.test.html} | 0
...m-network.html => forum-network.test.html} | 0
forum-network/src/tests/forum.html | 9 --
forum-network/src/tests/forum.test.html | 26 +++++
forum-network/src/tests/forum/forum.test.js | 79 -------------
forum-network/src/tests/graph.html | 33 ------
forum-network/src/tests/mocha.test.html | 28 +++++
.../{reputation.html => reputation.test.html} | 10 +-
forum-network/src/tests/scripts/forum.test.js | 110 ++++++++++++++++++
forum-network/src/tests/scripts/mocha.test.js | 23 ++++
forum-network/src/tests/scripts/wdag.test.js | 23 ++++
...on-pool.html => validation-pool.test.html} | 0
forum-network/src/tests/wdag.test.html | 9 ++
26 files changed, 384 insertions(+), 198 deletions(-)
create mode 100644 forum-network/src/classes/exchange.js
create mode 100644 forum-network/src/classes/list.js
create mode 100644 forum-network/src/classes/question.js
rename forum-network/src/classes/{graph.js => wdag.js} (63%)
rename forum-network/src/tests/{availability.html => availability.test.html} (100%)
rename forum-network/src/tests/{basic.html => basic.test.html} (100%)
rename forum-network/src/tests/{debounce.html => debounce.test.html} (100%)
rename forum-network/src/tests/{flowchart.html => flowchart.test.html} (100%)
rename forum-network/src/tests/{forum-network.html => forum-network.test.html} (100%)
delete mode 100644 forum-network/src/tests/forum.html
create mode 100644 forum-network/src/tests/forum.test.html
delete mode 100644 forum-network/src/tests/forum/forum.test.js
delete mode 100644 forum-network/src/tests/graph.html
create mode 100644 forum-network/src/tests/mocha.test.html
rename forum-network/src/tests/{reputation.html => reputation.test.html} (71%)
create mode 100644 forum-network/src/tests/scripts/forum.test.js
create mode 100644 forum-network/src/tests/scripts/mocha.test.js
create mode 100644 forum-network/src/tests/scripts/wdag.test.js
rename forum-network/src/tests/{validation-pool.html => validation-pool.test.html} (100%)
create mode 100644 forum-network/src/tests/wdag.test.html
diff --git a/forum-network/.eslintrc.js b/forum-network/.eslintrc.js
index 01170fe..1d9fdd3 100644
--- a/forum-network/.eslintrc.js
+++ b/forum-network/.eslintrc.js
@@ -30,4 +30,18 @@ module.exports = {
'no-constant-condition': ['off'],
'no-await-in-loop': ['off'],
},
+ globals: {
+ _: 'readonly',
+ chai: 'readonly',
+ expect: 'readonly',
+ mocha: 'readonly',
+ describe: 'readonly',
+ context: 'readonly',
+ it: 'readonly',
+ specify: 'readonly',
+ before: 'readonly',
+ after: 'readonly',
+ beforeEach: 'readonly',
+ afterEach: 'readonly',
+ },
};
diff --git a/forum-network/src/classes/exchange.js b/forum-network/src/classes/exchange.js
new file mode 100644
index 0000000..e69de29
diff --git a/forum-network/src/classes/forum-view.js b/forum-network/src/classes/forum-view.js
index e6a2d16..3c67def 100644
--- a/forum-network/src/classes/forum-view.js
+++ b/forum-network/src/classes/forum-view.js
@@ -1,4 +1,4 @@
-import { Graph } from './graph.js';
+import { WDAG } from './wdag.js';
class Author {
constructor() {
@@ -21,7 +21,7 @@ class PostVertex {
export class ForumView {
constructor() {
this.reputations = new Map();
- this.posts = new Graph();
+ this.posts = new WDAG();
this.authors = new Map();
}
@@ -53,8 +53,8 @@ export class ForumView {
const postVertex = new PostVertex(postId, author, stake, content, citations);
console.log('addPost', { id: postId, postContent });
this.posts.addVertex(postId, postVertex);
- for (const citation of citations) {
- this.posts.addEdge('citation', postId, citation.postId, citation);
+ for (const { postId: citedPostId, weight } of citations) {
+ this.posts.addEdge('citation', postId, citedPostId, weight);
}
this.applyNonbindingReputationEffects(postVertex);
}
diff --git a/forum-network/src/classes/forum.js b/forum-network/src/classes/forum.js
index c67008f..1bcbef3 100644
--- a/forum-network/src/classes/forum.js
+++ b/forum-network/src/classes/forum.js
@@ -1,17 +1,20 @@
import { Actor } from './actor.js';
-import { Graph } from './graph.js';
+import { WDAG } from './wdag.js';
import { Action } from './action.js';
import { CryptoUtil } from './crypto.js';
import params from '../params.js';
import { ReputationHolder } from './reputation-holder.js';
import { displayNumber } from '../util.js';
+const CITATION = 'citation';
+const BALANCE = 'balance';
+
class Post extends Actor {
constructor(forum, authorPublicKey, postContent) {
const index = forum.posts.countVertices();
const name = `Post${index + 1}`;
super(name, forum.scene);
- this.id = postContent.id ?? `post_${CryptoUtil.randomUUID()}`;
+ this.id = postContent.id ?? `post_${CryptoUtil.randomUUID().slice(0, 4)}`;
this.authorPublicKey = authorPublicKey;
this.value = 0;
this.initialValue = 0;
@@ -47,7 +50,7 @@ export class Forum extends ReputationHolder {
constructor(name, scene) {
super(`forum_${CryptoUtil.randomUUID()}`, name, scene);
this.id = this.reputationPublicKey;
- this.posts = new Graph(scene);
+ this.posts = new WDAG(scene);
this.actions = {
addPost: new Action('add post', scene),
propagateValue: new Action('propagate value', this.scene),
@@ -60,7 +63,7 @@ export class Forum extends ReputationHolder {
await this.actions.addPost.log(this, post);
this.posts.addVertex(post.id, post, post.getLabel());
for (const { postId: citedPostId, weight } of post.citations) {
- this.posts.addEdge('citation', post.id, citedPostId, { weight });
+ this.posts.addEdge(CITATION, post.id, citedPostId, weight);
}
return post.id;
}
@@ -88,7 +91,8 @@ export class Forum extends ReputationHolder {
}) {
this.activate();
const initialValue = bench.reputation.valueOf(tokenId);
- const post = this.getPost(postId);
+ const postVertex = this.posts.getVertex(postId);
+ const post = postVertex.data;
post.setStatus('Validated');
post.initialValue = initialValue;
this.posts.setVertexLabel(post.id, post.getLabel());
@@ -97,9 +101,13 @@ export class Forum extends ReputationHolder {
// so that its value can be updated by future validated posts.
post.tokenId = tokenId;
- // Compute rewards
const rewardsAccumulator = new Map();
- await this.propagateValue(rewardsAccumulator, this, post, initialValue);
+
+ // Compute rewards
+ await this.propagateValue(
+ { to: postVertex, from: { data: this } },
+ { rewardsAccumulator, increment: initialValue },
+ );
// Apply computed rewards to update values of tokens
for (const [id, value] of rewardsAccumulator) {
@@ -118,46 +126,62 @@ export class Forum extends ReputationHolder {
this.deactivate();
}
- async propagateValue(rewardsAccumulator, fromActor, post, increment, depth = 0) {
- this.actions.propagateValue.log(fromActor, post, `(${increment})`);
+ /**
+ * @param {Edge} edge
+ * @param {Object} opaqueData
+ */
+ async propagateValue(edge, { rewardsAccumulator, increment, depth = 0 }) {
+ const postVertex = edge.to;
+ const post = postVertex?.data;
+
+ this.actions.propagateValue.log(edge.from.data, post, `(${increment})`);
+
+ console.log('propagateValue start', {
+ from: edge.from.id,
+ to: edge.to.id,
+ depth,
+ value: post.value,
+ increment,
+ });
- // Recursively distribute reputation to citations, according to weights
- let totalOutboundAmount = 0;
if (params.referenceChainLimit === null || depth <= params.referenceChainLimit) {
- for (const { postId: citedPostId, weight } of post.citations) {
- const citedPost = this.getPost(citedPostId);
+ for (const citationEdge of this.posts.getEdges(CITATION, post)) {
+ const { to: citedPostVertex, data: { weight } } = citationEdge;
+ const citedPost = citedPostVertex.data;
let outboundAmount = weight * increment;
- // If this is a negative citation, it must not bring the target below zero value.
+ const balance = this.posts.getEdge(BALANCE, postVertex, citedPostVertex)?.data || 0;
+ console.log('Citation', {
+ citationEdge, outboundAmount, balance, citedPostValue: citedPost.value,
+ });
+ // We need to ensure that we propagate no more reputation than we leached
if (outboundAmount < 0) {
- const citedPostTotalCitationWeight = citedPost.citations.reduce((t, { weight: w }) => t += w, 0);
- const citedPostCapacity = citedPost.value / (1 - citedPostTotalCitationWeight);
- outboundAmount = Math.max(outboundAmount, -citedPostCapacity);
+ outboundAmount = Math.max(outboundAmount, -citedPost.value);
+ if (depth > 0) {
+ outboundAmount = Math.max(outboundAmount, -balance);
+ }
}
- const refundFromOutbound = await this.propagateValue(
+ increment -= outboundAmount * params.leachingValue;
+ this.posts.setEdge(BALANCE, postVertex, citedPostVertex, balance + outboundAmount);
+ await this.propagateValue(citationEdge, {
rewardsAccumulator,
- post,
- citedPost,
- outboundAmount,
- depth + 1,
- );
- totalOutboundAmount += outboundAmount - refundFromOutbound;
+ increment: outboundAmount,
+ depth: depth + 1,
+ });
}
}
- // Apply leaching value
- const incrementAfterLeaching = increment - totalOutboundAmount * params.leachingValue;
+ const newValue = post.value + increment;
- const rawNewValue = post.value + incrementAfterLeaching;
- const newValue = Math.max(0, rawNewValue);
- const appliedIncrement = newValue - post.value;
- const refundToInbound = increment - appliedIncrement;
+ console.log('propagateValue end', {
+ depth,
+ increment,
+ newValue,
+ });
// Award reputation to post author
- rewardsAccumulator.set(post.tokenId, appliedIncrement);
+ rewardsAccumulator.set(post.tokenId, increment);
// Increment the value of the post
await this.setPostValue(post, newValue);
-
- return refundToInbound;
}
}
diff --git a/forum-network/src/classes/list.js b/forum-network/src/classes/list.js
new file mode 100644
index 0000000..8dc58ac
--- /dev/null
+++ b/forum-network/src/classes/list.js
@@ -0,0 +1,9 @@
+export class List {
+ constructor() {
+ this.items = [];
+ }
+
+ add(item) {
+ this.items.push(item);
+ }
+}
diff --git a/forum-network/src/classes/question.js b/forum-network/src/classes/question.js
new file mode 100644
index 0000000..e69de29
diff --git a/forum-network/src/classes/scene.js b/forum-network/src/classes/scene.js
index 0d33af9..294fa22 100644
--- a/forum-network/src/classes/scene.js
+++ b/forum-network/src/classes/scene.js
@@ -2,6 +2,7 @@ import mermaid from 'https://unpkg.com/mermaid@9.2.2/dist/mermaid.esm.min.mjs';
import { Actor } from './actor.js';
import { Action } from './action.js';
import { debounce, hexToRGB } from '../util.js';
+import { CryptoUtil } from './crypto.js';
class MermaidDiagram {
constructor(box, logBox) {
@@ -90,10 +91,11 @@ export class Scene {
this.box.addBox('Spacer').setInnerHTML(' ');
this.topSection = this.box.addBox('Top section').flex();
this.displayValuesBox = this.topSection.addBox('Values');
- this.middleSection = this.box.addBox('Middle section').flex();
+ this.middleSection = this.box.addBox('Middle section');
this.box.addBox('Spacer').setInnerHTML(' ');
this.actors = new Set();
this.dateStart = new Date();
+ this.flowcharts = new Map();
mermaid.mermaidAPI.initialize({
startOnLoad: false,
@@ -117,26 +119,49 @@ export class Scene {
withSequenceDiagram() {
const box = this.box.addBox('Sequence diagram');
this.box.addBox('Spacer').setInnerHTML(' ');
- const logBox = this.box.addBox('Sequence diagram text');
+ const logBox = this.box.addBox('Sequence diagram text').addClass('dim');
this.sequence = new MermaidDiagram(box, logBox);
this.sequence.log('sequenceDiagram', false);
return this;
}
- withFlowchart(direction = 'BT') {
- const box = this.topSection.addBox('Flowchart');
- this.box.addBox('Spacer').setInnerHTML(' ');
- const logBox = this.box.addBox('Flowchart text');
+ withFlowchart({ direction = 'BT' } = {}) {
+ const box = this.topSection.addBox('Flowchart').addClass('padded');
+ const logBox = this.topSection.addBox('Flowchart text').addClass('dim');
this.flowchart = new MermaidDiagram(box, logBox);
this.flowchart.log(`graph ${direction}`, false);
return this;
}
+ withAdditionalFlowchart({ id, name, direction = 'BT' } = {}) {
+ const index = this.flowcharts.size;
+ name = name ?? `Flowchart ${index}`;
+ id = id ?? `flowchart_${CryptoUtil.randomUUID().slice(0, 4)}`;
+ const container = this.middleSection.addBox(name).flex();
+ const box = container.addBox('Flowchart').addClass('padded');
+ const logBox = container.addBox('Flowchart text').addClass('dim');
+ const flowchart = new MermaidDiagram(box, logBox);
+ flowchart.log(`graph ${direction}`, false);
+ this.flowcharts.set(id, flowchart);
+ return this;
+ }
+
+ lastFlowchart() {
+ if (!this.flowcharts.size) {
+ if (this.flowchart) {
+ return this.flowchart;
+ }
+ throw new Error('lastFlowchart: No additional flowcharts have been added.');
+ }
+ const flowcharts = Array.from(this.flowcharts.values());
+ return flowcharts[flowcharts.length - 1];
+ }
+
withTable() {
if (this.table) {
return this;
}
- const box = this.middleSection.addBox('Table');
+ const box = this.middleSection.addBox('Table').addClass('padded');
this.box.addBox('Spacer').setInnerHTML(' ');
this.table = new Table(box);
return this;
diff --git a/forum-network/src/classes/graph.js b/forum-network/src/classes/wdag.js
similarity index 63%
rename from forum-network/src/classes/graph.js
rename to forum-network/src/classes/wdag.js
index 29a8a7a..58000aa 100644
--- a/forum-network/src/classes/graph.js
+++ b/forum-network/src/classes/wdag.js
@@ -16,20 +16,27 @@ class Vertex {
}
class Edge {
- constructor(label, from, to, data) {
+ constructor(label, from, to, weight) {
this.from = from;
this.to = to;
this.label = label;
- this.data = data;
+ this.weight = weight;
}
}
-export class Graph {
+export class WDAG {
constructor(scene) {
this.scene = scene;
this.vertices = new Map();
this.edgeLabels = new Map();
this.nextVertexId = 0;
+ this.flowchart = scene?.flowchart ?? null;
+ }
+
+ withFlowchart() {
+ this.scene.withAdditionalFlowchart();
+ this.flowchart = this.scene.lastFlowchart();
+ return this;
}
addVertex(id, data, label) {
@@ -43,16 +50,12 @@ export class Graph {
}
const vertex = new Vertex(id, data);
this.vertices.set(id, vertex);
- if (this.scene && this.scene.flowchart) {
- this.scene.flowchart.log(`${id}[${label ?? id}]`);
- }
+ this.flowchart?.log(`${id}[${label ?? id}]`);
return this;
}
setVertexLabel(id, label) {
- if (this.scene && this.scene.flowchart) {
- this.scene.flowchart.log(`${id}[${label}]`);
- }
+ this.flowchart?.log(`${id}[${label}]`);
}
getVertex(id) {
@@ -68,36 +71,43 @@ export class Graph {
}
getEdge(label, from, to) {
+ from = from instanceof Vertex ? from : this.getVertex(from);
+ to = to instanceof Vertex ? to : this.getVertex(to);
const edges = this.edgeLabels.get(label);
- return edges?.get(JSON.stringify({ from, to }));
+ return edges?.get(JSON.stringify([from.id, to.id]));
}
setEdge(label, from, to, edge) {
+ from = from instanceof Vertex ? from : this.getVertex(from);
+ to = to instanceof Vertex ? to : this.getVertex(to);
+ if (!(edge instanceof Edge)) {
+ edge = new Edge(edge);
+ }
let edges = this.edgeLabels.get(label);
if (!edges) {
edges = new Map();
this.edgeLabels.set(label, edges);
}
- edges.set(JSON.stringify({ from, to }), edge);
+ edges.set(JSON.stringify([from.id, to.id]), edge);
}
- addEdge(label, from, to, data) {
+ addEdge(label, from, to, weight) {
+ from = from instanceof Vertex ? from : this.getVertex(from);
+ to = to instanceof Vertex ? to : this.getVertex(to);
if (this.getEdge(label, from, to)) {
throw new Error(`Edge ${label} from ${from} to ${to} already exists`);
}
- const edge = new Edge(label, from, to, data);
+ const edge = new Edge(label, from, to, weight);
this.setEdge(label, from, to, edge);
- const fromVertex = this.getVertex(from);
- fromVertex.edges.from.push(edge);
- const toVertex = this.getVertex(to);
- toVertex.edges.to.push(edge);
- if (this.scene && this.scene.flowchart) {
- this.scene.flowchart.log(`${fromVertex.id} -- ${data.weight} --> ${toVertex.id}`);
- }
+ from.edges.from.push(edge);
+ to.edges.to.push(edge);
+ this.flowchart?.log(`${from.id} -- ${weight} --> ${to.id}`);
return this;
}
getEdges(label, from, to) {
+ from = from instanceof Vertex ? from : this.getVertex(from);
+ to = to instanceof Vertex ? to : this.getVertex(to);
const edgeLabels = label ? [label] : Array.from(this.edgeLabels.keys());
return edgeLabels.flatMap((edgeLabel) => {
const edges = this.edgeLabels.get(edgeLabel);
diff --git a/forum-network/src/index.css b/forum-network/src/index.css
index 98c570b..262dc7c 100644
--- a/forum-network/src/index.css
+++ b/forum-network/src/index.css
@@ -29,6 +29,12 @@ a:visited {
font-family: monospace;
font-size: 8pt;
}
+.dim {
+ opacity: 0.25;
+}
+.padded {
+ padding: 20px;
+}
svg {
width: 800px;
}
diff --git a/forum-network/src/index.html b/forum-network/src/index.html
index bb90cf3..a3c3dbe 100644
--- a/forum-network/src/index.html
+++ b/forum-network/src/index.html
@@ -7,19 +7,19 @@
Tests
Primary
Secondary
Tertiary
-
-
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/forum-network/src/tests/availability.html b/forum-network/src/tests/availability.test.html
similarity index 100%
rename from forum-network/src/tests/availability.html
rename to forum-network/src/tests/availability.test.html
diff --git a/forum-network/src/tests/basic.html b/forum-network/src/tests/basic.test.html
similarity index 100%
rename from forum-network/src/tests/basic.html
rename to forum-network/src/tests/basic.test.html
diff --git a/forum-network/src/tests/debounce.html b/forum-network/src/tests/debounce.test.html
similarity index 100%
rename from forum-network/src/tests/debounce.html
rename to forum-network/src/tests/debounce.test.html
diff --git a/forum-network/src/tests/flowchart.html b/forum-network/src/tests/flowchart.test.html
similarity index 100%
rename from forum-network/src/tests/flowchart.html
rename to forum-network/src/tests/flowchart.test.html
diff --git a/forum-network/src/tests/forum-network.html b/forum-network/src/tests/forum-network.test.html
similarity index 100%
rename from forum-network/src/tests/forum-network.html
rename to forum-network/src/tests/forum-network.test.html
diff --git a/forum-network/src/tests/forum.html b/forum-network/src/tests/forum.html
deleted file mode 100644
index 511012f..0000000
--- a/forum-network/src/tests/forum.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/forum-network/src/tests/forum.test.html b/forum-network/src/tests/forum.test.html
new file mode 100644
index 0000000..7403a5c
--- /dev/null
+++ b/forum-network/src/tests/forum.test.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
diff --git a/forum-network/src/tests/forum/forum.test.js b/forum-network/src/tests/forum/forum.test.js
deleted file mode 100644
index 70acbf3..0000000
--- a/forum-network/src/tests/forum/forum.test.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import { Box } from '../../classes/box.js';
-import { Scene } from '../../classes/scene.js';
-import { Expert } from '../../classes/expert.js';
-import { Bench } from '../../classes/bench.js';
-import { delay } from '../../util.js';
-import { Forum } from '../../classes/forum.js';
-import { PostContent } from '../../classes/post-content.js';
-import params from '../../params.js';
-
-const DEFAULT_DELAY_INTERVAL = 500;
-
-const rootElement = document.getElementById('forum-test');
-const rootBox = new Box('rootBox', rootElement).flex();
-
-const scene = (window.scene = new Scene('Forum test', rootBox));
-scene.withSequenceDiagram();
-scene.withFlowchart();
-scene.withTable();
-
-scene.addDisplayValue('c3. stakeForAuthor').set(params.stakeForAuthor);
-scene.addDisplayValue('q2. revaluationLimit').set(params.revaluationLimit);
-scene
- .addDisplayValue('q3. referenceChainLimit')
- .set(params.referenceChainLimit);
-scene.addDisplayValue('q4. leachingValue').set(params.leachingValue);
-scene.addDisplayValue(' ');
-
-const experts = (window.experts = []);
-const newExpert = async () => {
- const index = experts.length;
- const name = `Expert${index + 1}`;
- const expert = await new Expert(name, scene).initialize();
- experts.push(expert);
- return expert;
-};
-
-const forum = (window.forum = new Forum('Forum', scene));
-const bench = (window.bench = new Bench(forum, 'Bench', scene));
-const expert1 = await newExpert();
-const expert2 = await newExpert();
-const expert3 = await newExpert();
-
-bench.addValue('total rep', () => bench.reputation.getTotal());
-forum.addValue('total value', () => forum.getTotalValue());
-
-for (const expert of experts) {
- expert.addValue('rep', () => bench.reputation.valueOwnedBy(expert.reputationPublicKey));
-}
-
-const addPost = async (author, title, fee, citations = []) => {
- await scene.startSection();
-
- const postContent = new PostContent({}).setTitle(title);
- for (const { postId, weight } of citations) {
- postContent.addCitation(postId, weight);
- }
-
- const { pool, postId } = await author.submitPostWithFee(
- bench,
- forum,
- postContent,
- {
- fee,
- duration: 1000,
- tokenLossRatio: 1,
- },
- );
- await delay(1000);
- await pool.evaluateWinningConditions();
- await scene.endSection();
- await delay(DEFAULT_DELAY_INTERVAL);
- return postId;
-};
-
-const postId1 = await addPost(expert1, 'Post 1', 20);
-const postId2 = await addPost(expert2, 'Post 2', 10, [{ postId: postId1, weight: 0.5 }]);
-const postId3 = await addPost(expert3, 'Post 3', 10, [{ postId: postId1, weight: -1 }]);
-await addPost(expert3, 'Post 4', 100, [{ postId: postId2, weight: -1 }]);
-await addPost(expert1, 'Post 5', 100, [{ postId: postId3, weight: -1 }]);
diff --git a/forum-network/src/tests/graph.html b/forum-network/src/tests/graph.html
deleted file mode 100644
index d3d605f..0000000
--- a/forum-network/src/tests/graph.html
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
diff --git a/forum-network/src/tests/mocha.test.html b/forum-network/src/tests/mocha.test.html
new file mode 100644
index 0000000..b5b9a6a
--- /dev/null
+++ b/forum-network/src/tests/mocha.test.html
@@ -0,0 +1,28 @@
+
+
+