From de4df434839bfa7394b15b14a33fd9c6dbb31fa5 Mon Sep 17 00:00:00 2001 From: Guilherme Correia Date: Thu, 24 Aug 2023 00:23:48 -0300 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=9A=91=20Move=20`webpack-dev-middlewa?= =?UTF-8?q?re`=20to=20prod=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 005279b..a26b043 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,7 @@ "swc-plugin-nullstack": "0.1.3", "terser-webpack-plugin": "5.3.6", "webpack": "5.88.1", - "webpack-hot-middleware": "2.25.4" - }, - "devDependencies": { + "webpack-hot-middleware": "2.25.4", "webpack-dev-middleware": "github:Mortaro/webpack-dev-middleware#fix-write-to-disk-cleanup" } } \ No newline at end of file From 1f48102fcc948e173e814be30e76d6342effc32b Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Thu, 24 Aug 2023 12:03:09 -0300 Subject: [PATCH 2/8] :bookmark: version 0.20.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a26b043..afd9f8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nullstack", - "version": "0.20.1", + "version": "0.20.2", "description": "Feature-Driven Full Stack JavaScript Components", "main": "./types/index.d.ts", "author": "Mortaro", From 4acab9d496d0c890256e5f1c2543a7c0f7642f2f Mon Sep 17 00:00:00 2001 From: Guilherme Correia Date: Sat, 26 Aug 2023 21:04:48 -0300 Subject: [PATCH 3/8] =?UTF-8?q?=F0=9F=9A=91=E2=AC=86=EF=B8=8F=20Update=20w?= =?UTF-8?q?ebpack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afd9f8b..9a1a209 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "swc-loader": "0.2.3", "swc-plugin-nullstack": "0.1.3", "terser-webpack-plugin": "5.3.6", - "webpack": "5.88.1", + "webpack": "5.88.2", "webpack-hot-middleware": "2.25.4", "webpack-dev-middleware": "github:Mortaro/webpack-dev-middleware#fix-write-to-disk-cleanup" } From 62d753c838720b44ebac1e9703abe2c72f75642c Mon Sep 17 00:00:00 2001 From: Guilherme Correia Date: Tue, 29 Aug 2023 06:32:52 -0300 Subject: [PATCH 4/8] =?UTF-8?q?=F0=9F=93=8C=20Move=20`webpack-dev-middlewa?= =?UTF-8?q?re`=20from=20git=20to=20latest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index afd9f8b..5fda410 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "terser-webpack-plugin": "5.3.6", "webpack": "5.88.1", "webpack-hot-middleware": "2.25.4", - "webpack-dev-middleware": "github:Mortaro/webpack-dev-middleware#fix-write-to-disk-cleanup" + "webpack-dev-middleware": "6.1.1" } } \ No newline at end of file From fb4c5bf18c353ef5e9ef5c6ca9373818d87ce9ed Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Tue, 29 Aug 2023 16:53:47 -0300 Subject: [PATCH 5/8] :bookmark: version 0.20.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5db3987..83712d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nullstack", - "version": "0.20.2", + "version": "0.20.3", "description": "Feature-Driven Full Stack JavaScript Components", "main": "./types/index.d.ts", "author": "Mortaro", From 73df30ccd507347b65b9e46a1dd39ee6cb7c728d Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Mon, 1 Sep 2025 15:31:50 -0300 Subject: [PATCH 6/8] :sparkles: svg support --- client/render.js | 9 ++++--- client/rerender.js | 14 ++++++----- shared/nodes.js | 2 +- tests/src/Application.njs | 2 ++ tests/src/SvgSupport.njs | 46 ++++++++++++++++++++++++++++++++++++ tests/src/SvgSupport.test.js | 22 +++++++++++++++++ 6 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 tests/src/SvgSupport.njs create mode 100644 tests/src/SvgSupport.test.js diff --git a/client/render.js b/client/render.js index 7fee343..e137652 100644 --- a/client/render.js +++ b/client/render.js @@ -5,7 +5,7 @@ import { anchorableElement } from './anchorableNode' import { generateCallback, generateSubject } from './events' import { ref } from './ref' -export default function render(node, options) { +export default function render(node, isSvg = false) { if (isFalse(node) || node.type === 'head') { node.element = document.createComment('') return node.element @@ -16,9 +16,8 @@ export default function render(node, options) { return node.element } - const svg = (options && options.svg) || node.type === 'svg' - - if (svg) { + isSvg = isSvg || node.type === 'svg' + if (isSvg) { node.element = document.createElementNS('http://www.w3.org/2000/svg', node.type) } else { node.element = document.createElement(node.type) @@ -58,7 +57,7 @@ export default function render(node, options) { if (!node.attributes.html) { for (let i = 0; i < node.children.length; i++) { - const child = render(node.children[i], { svg }) + const child = render(node.children[i], isSvg) node.element.appendChild(child) } diff --git a/client/rerender.js b/client/rerender.js index cd788f6..512f9d6 100644 --- a/client/rerender.js +++ b/client/rerender.js @@ -100,7 +100,7 @@ function updateHeadChildren(currentChildren, nextChildren) { } } -function _rerender(current, next) { +function _rerender(current, next, isParentSvg = false) { const selector = current.element next.element = current.element @@ -108,8 +108,10 @@ function _rerender(current, next) { return } + const isSvg = isParentSvg || next.type === 'svg' + if (current.type !== next.type) { - const nextSelector = render(next) + const nextSelector = render(next, isSvg) selector.replaceWith(nextSelector) return } @@ -132,22 +134,22 @@ function _rerender(current, next) { const limit = Math.max(current.children.length, next.children.length) if (next.children.length > current.children.length) { for (let i = 0; i < current.children.length; i++) { - _rerender(current.children[i], next.children[i]) + _rerender(current.children[i], next.children[i], isSvg) } for (let i = current.children.length; i < next.children.length; i++) { - const nextSelector = render(next.children[i]) + const nextSelector = render(next.children[i], isSvg) selector.appendChild(nextSelector) } } else if (current.children.length > next.children.length) { for (let i = 0; i < next.children.length; i++) { - _rerender(current.children[i], next.children[i]) + _rerender(current.children[i], next.children[i], isSvg) } for (let i = current.children.length - 1; i >= next.children.length; i--) { selector.childNodes[i].remove() } } else { for (let i = limit - 1; i > -1; i--) { - _rerender(current.children[i], next.children[i]) + _rerender(current.children[i], next.children[i], isSvg) } } } diff --git a/shared/nodes.js b/shared/nodes.js index f1d3363..4da2564 100644 --- a/shared/nodes.js +++ b/shared/nodes.js @@ -18,5 +18,5 @@ export function isFunction(node) { } export function isText(node) { - return node.type === 'text' + return node.type === 'text' && node.attributes === undefined } diff --git a/tests/src/Application.njs b/tests/src/Application.njs index 58691b9..5bd8201 100644 --- a/tests/src/Application.njs +++ b/tests/src/Application.njs @@ -63,6 +63,7 @@ import LazyComponentLoader from './LazyComponentLoader' import NestedFolder from './nested/NestedFolder' import ChildComponentWithoutServerFunctions from './ChildComponentWithoutServerFunctions' import ObjectEventScope from './ObjectEventScope' +import SvgSupport from './SvgSupport.njs' import './Application.css' class Application extends Nullstack { @@ -156,6 +157,7 @@ class Application extends Nullstack { + ) diff --git a/tests/src/SvgSupport.njs b/tests/src/SvgSupport.njs new file mode 100644 index 0000000..6dcc98e --- /dev/null +++ b/tests/src/SvgSupport.njs @@ -0,0 +1,46 @@ +import Nullstack from 'nullstack'; + +function Close({ size }) { + return ( + + + + + ) +} + +function Hamburger({ size }) { + return ( + + + + + + ) +} + +class SvgSupport extends Nullstack { + + open = false + visible = false + + render() { + return ( +
+ + I + love + my + cat! + + {this.open ? : } + + {this.visible && } + +
+ ) + } + +} + +export default SvgSupport; \ No newline at end of file diff --git a/tests/src/SvgSupport.test.js b/tests/src/SvgSupport.test.js new file mode 100644 index 0000000..0a966a6 --- /dev/null +++ b/tests/src/SvgSupport.test.js @@ -0,0 +1,22 @@ +describe('SvgSupport', () => { + beforeEach(async () => { + await page.goto('http://localhost:6969/svg-support') + await page.waitForSelector('[data-hydrated]') + }) + + test('svg can render text', async () => { + expect(true).toBeTruthy() + }) + + test('svg can add new paths while rerendering', async () => { + expect(true).toBeTruthy() + }) + + test('svg can render in short circuit statements', async () => { + expect(true).toBeTruthy() + }) + + test('svg can render in ternary statements', async () => { + expect(true).toBeTruthy() + }) +}) From 0b51beb8097a7610645d1882bc6d2c1a621a814e Mon Sep 17 00:00:00 2001 From: Aylon Muramatsu Date: Mon, 6 Oct 2025 17:19:40 -0300 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20implementa=20testes=20e=20adiciona?= =?UTF-8?q?=20corre=C3=A7=C3=A3o=20para=20render=20do=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/render.js | 4 +- tests/src/SvgSupport.njs | 2 +- tests/src/SvgSupport.test.js | 96 ++++++++++++++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/server/render.js b/server/render.js index e1b565d..06e416c 100644 --- a/server/render.js +++ b/server/render.js @@ -1,4 +1,4 @@ -import { isFalse } from '../shared/nodes' +import { isFalse, isText } from '../shared/nodes' import { sanitizeHtml } from '../shared/sanitizeString' import renderAttributes from './renderAttributes' @@ -25,7 +25,7 @@ function renderBody(node, scope, next) { if (isFalse(node)) { return '' } - if (node.type === 'text') { + if (isText(node)) { const text = node.text === '' ? ' ' : sanitizeHtml(node.text.toString()) return next && next.type === 'text' ? `${text}` : text } diff --git a/tests/src/SvgSupport.njs b/tests/src/SvgSupport.njs index 6dcc98e..9080bc9 100644 --- a/tests/src/SvgSupport.njs +++ b/tests/src/SvgSupport.njs @@ -26,7 +26,7 @@ class SvgSupport extends Nullstack { render() { return ( -
+
I love diff --git a/tests/src/SvgSupport.test.js b/tests/src/SvgSupport.test.js index 0a966a6..b651061 100644 --- a/tests/src/SvgSupport.test.js +++ b/tests/src/SvgSupport.test.js @@ -5,18 +5,106 @@ describe('SvgSupport', () => { }) test('svg can render text', async () => { - expect(true).toBeTruthy() + // Verifica se o SVG possui 4 elementos dentro dele + const svg = await page.$('svg'); + const texts = await svg.$$('text'); + expect(texts.length).toBe(4); }) test('svg can add new paths while rerendering', async () => { - expect(true).toBeTruthy() + // Verifica se o ícone Hamburger está presente inicialmente (3 paths) + const hamburgerPaths = await page.$$('svg[width="30"] path') + expect(hamburgerPaths.length).toBe(3) // Hamburger has 3 paths }) test('svg can render in short circuit statements', async () => { - expect(true).toBeTruthy() + // Verifica se o ícone de Hamburger está sendo exibido (3 paths) + const hamburgerPaths = await page.$$('svg[width="30"] path') + expect(hamburgerPaths.length).toBe(3) }) test('svg can render in ternary statements', async () => { - expect(true).toBeTruthy() + let bigHamburger = await page.$('svg[width="69"]') + expect(bigHamburger).toBeFalsy() + + // Clica no segundo botão (show) + const buttons = await page.$$('button') + await buttons[1].click() + + // Aguarda o Hamburger grande aparecer + await page.waitForSelector('svg[width="69"]') + + // Verifica se o Hamburger foi renderizado no ternário + bigHamburger = await page.$('svg[width="69"]') + expect(bigHamburger).toBeTruthy() + + }) + + test('icon toggle functionality works correctly', async () => { + // Primeiro verifica o estado inicial (deve ser Hamburger, 3 paths) + let iconPaths = await page.$$('svg[width="30"] path') + expect(iconPaths.length).toBe(3) // Hamburger tem 3 paths + + // Clica no primeiro botão (toggle) + const buttons = await page.$$('button') + await buttons[0].click() + + // Aguarda o ícone trocar (Close tem 2 paths) + await page.waitForFunction(() => { + const svg = document.querySelector('svg[width="30"]'); + return svg && svg.querySelectorAll('path').length === 2; + }); + + iconPaths = await page.$$('svg[width="30"] path') + expect(iconPaths.length).toBe(2) // Close tem 2 paths + + // Clica novamente para voltar ao Hamburger + await buttons[0].click() + + // Aguarda o ícone trocar de volta (Hamburger tem 3 paths) + await page.waitForFunction(() => { + const svg = document.querySelector('svg[width="30"]'); + return svg && svg.querySelectorAll('path').length === 3; + }); + + iconPaths = await page.$$('svg[width="30"] path') + expect(iconPaths.length).toBe(3) // Hamburger tem 3 paths + }) + + test('icon visibility toggle works correctly', async () => { + + // Verifica que o ícone grande não está visível inicialmente + let bigHamburger = await page.$('svg[width="69"]') + expect(bigHamburger).toBeFalsy() + + // Clica no segundo botão (show) + const buttons = await page.$$('button') + await buttons[1].click() + + // Aguarda o Hamburger grande aparecer + await page.waitForSelector('svg[width="69"]') + + // Verifica se o Hamburger grande apareceu + bigHamburger = await page.$('svg[width="69"]') + expect(bigHamburger).toBeTruthy() + + // Clica novamente no segundo botão (show) para esconder + await buttons[1].click() + + // Aguarda o Hamburger grande desaparecer do DOM + await page.waitForSelector('svg[width="69"]', { hidden: true }) + + // Verifica se o Hamburger grande desapareceu + bigHamburger = await page.$('svg[width="69"]') + expect(bigHamburger).toBeFalsy() + }) + + test('svg attributes are correctly applied', async () => { + // Verifica se os atributos SVG estão sendo aplicados corretamente + const svgElement = await page.$('svg[viewBox="0 0 240 80"]') + expect(svgElement).toBeTruthy() + + const xmlns = await page.$eval('svg[viewBox="0 0 240 80"]', el => el.getAttribute('xmlns')) + expect(xmlns).toBe('http://www.w3.org/2000/svg') }) }) From ec5e51be540a98bab311ab02eef5c9c0231ec017 Mon Sep 17 00:00:00 2001 From: Christian Mortaro Date: Thu, 9 Oct 2025 22:21:05 -0300 Subject: [PATCH 8/8] :bookmark: version 0.20.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83712d5..970fece 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nullstack", - "version": "0.20.3", + "version": "0.20.4", "description": "Feature-Driven Full Stack JavaScript Components", "main": "./types/index.d.ts", "author": "Mortaro",