Repository: ProjectEvergreen/greenwood (js)

Issue:
getting a `Cannot read properties of null (reading 'rawAttrs')` error
<!--
# PLEASE READ FIRST
If asking a question, looking for help, or you have an idea for Greenwood, please consider opening a discussion first or chatting with us in our Discord.
-->

## Type of Change

Bug

## Summary

In this project it was observed that at times [this line](https://github.com/ProjectEvergreen/greenwood/blob/v0.30.2/packages/cli/src/lib/layout-utils.js#L215) in Greenwood would fail in [this project](https://github.com/thescientist13/HP_Stuff/pull/1)
```sh
TypeError: Cannot read properties of null (reading 'rawAttrs')
    at getAppLayout (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/@greenwood+cli@0.31.0-alpha.1_patch_hash=pkl2llrpdlpyhjjur27kchnsuq/node_modules/@greenwood/cli/src/lib/layout-utils.js:221:66)
    at async StandardHtmlResource.serve (file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/@greenwood+cli@0.31.0-alpha.1_patch_hash=pkl2llrpdlpyhjjur27kchnsuq/node_modules/@greenwood/cli/src/plugins/resource/plugin-standard-html.js:125:12)
    at async file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/@greenwood+cli@0.31.0-alpha.1_patch_hash=pkl2llrpdlpyhjjur27kchnsuq/node_modules/@greenwood/cli/src/lifecycles/serve.js:69:27
    at async file:///Users/owenbuckley/Workspace/github-forks/HP_Stuff/node_modules/.pnpm/@greenwood+cli@0.31.0-alpha.1_patch_hash=pkl2llrpdlpyhjjur27kchnsuq/node_modules/@greenwood/cli/src/lifecycles/serve.js:55:5
```

## Details

Not really sure why so will need to play around with exactly why to find a reproduction case, but this simple fix seemed to work at least
```js
const mergedHtml = pageRoot && pageRoot.querySelector('html')?.rawAttrs
  ? `<html ${pageRoot.querySelector('html').rawAttrs}>`
  : appRoot.querySelector('html').rawAttrs !== ''
    ? `<html ${appRoot.querySelector('html').rawAttrs}>`
    '<html>';
```

All the pages in question seemed to have an `<html>` tag though?

Pull request:
bug/issue 1353 handle pages with no wrapping `<html>` tag when building up final output HTML
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue

resolves #1353 

## Documentation 

N / A

## Summary of Changes

1. More gracefully handle pages with no wrapping `<html>` tag
1. Add reproduction and test case

Patch:
diff --git a/packages/cli/src/lib/layout-utils.js b/packages/cli/src/lib/layout-utils.js
index 08f2acc6..530bdd55 100644
--- a/packages/cli/src/lib/layout-utils.js
+++ b/packages/cli/src/lib/layout-utils.js
@@ -218,9 +218,9 @@ async function getAppLayout(pageLayoutContents, compilation, customImports = [],
             : matchingRoute.label;
     }
 
-    const mergedHtml = pageRoot && pageRoot.querySelector('html').rawAttrs !== ''
+    const mergedHtml = pageRoot && pageRoot.querySelector('html') && pageRoot.querySelector('html')?.rawAttrs !== ''
       ? `<html ${pageRoot.querySelector('html').rawAttrs}>`
-      : appRoot.querySelector('html').rawAttrs !== ''
+      : appRoot && appRoot.querySelector('html') && appRoot.querySelector('html')?.rawAttrs !== ''
         ? `<html ${appRoot.querySelector('html').rawAttrs}>`
         : '<html>';
 


Test patch:
diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/build.default.workspace-layouts-page-and-app-dynamic.spec.js b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/build.default.workspace-layouts-page-and-app-dynamic.spec.js
index 7b56a55b..1b8a0cbe 100644
--- a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/build.default.workspace-layouts-page-and-app-dynamic.spec.js
+++ b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/build.default.workspace-layouts-page-and-app-dynamic.spec.js
@@ -14,7 +14,8 @@
  * User Workspace
  * src/
  *   pages/
- *     index.md
+ *     about.md
+ *     index.html
  *   layouts/
  *     app.js
  *     page.js
@@ -50,7 +51,7 @@ describe('Build Greenwood With: ', function() {
 
     runSmokeTest(['public'], LABEL);
 
-    describe('Custom App and Page Layout', function() {
+    describe('Custom App and page with no <html> wrapping element', function() {
       let dom;
 
       before(async function() {
@@ -71,6 +72,53 @@ describe('Build Greenwood With: ', function() {
         expect(heading[0].textContent).to.equal('App Layout');
       });
 
+      it('should not have the expected <h2> tag from the dynamic page layout', function() {
+        const heading = dom.window.document.querySelectorAll('h2');
+
+        expect(heading.length).to.equal(0);
+      });
+
+      it('should have the expected content from the index.md', function() {
+        const heading = dom.window.document.querySelectorAll('h3');
+        const paragraph = dom.window.document.querySelectorAll('p');
+
+        expect(heading.length).to.equal(1);
+        expect(heading[0].textContent).to.equal('Home Page');
+
+        expect(paragraph.length).to.equal(1);
+        expect(paragraph[0].textContent).to.equal('Coffey was here');
+      });
+
+      it('should have the expected <footer> tag from the dynamic app layout', function() {
+        const year = new Date().getFullYear();
+        const footer = dom.window.document.querySelectorAll('footer');
+
+        expect(footer.length).to.equal(1);
+        expect(footer[0].textContent).to.equal(`${year}`);
+      });
+    });
+
+    describe('Custom App and Page Layout', function() {
+      let dom;
+
+      before(async function() {
+        dom = await JSDOM.fromFile(path.resolve(this.context.publicDir, 'about/index.html'));
+      });
+
+      it('should have the expected <title> tag from the dynamic app layout', function() {
+        const title = dom.window.document.querySelectorAll('head title');
+
+        expect(title.length).to.equal(1);
+        expect(title[0].textContent).to.equal('App Layout');
+      });
+
+      it('should have the expected <h1> tag from the dynamic app layout', function() {
+        const heading = dom.window.document.querySelectorAll('h1');
+
+        expect(heading.length).to.equal(1);
+        expect(heading[0].textContent).to.equal('App Layout');
+      });
+
       it('should have the expected <h2> tag from the dynamic page layout', function() {
         const heading = dom.window.document.querySelectorAll('h2');
 
@@ -83,10 +131,10 @@ describe('Build Greenwood With: ', function() {
         const paragraph = dom.window.document.querySelectorAll('p');
 
         expect(heading.length).to.equal(1);
-        expect(heading[0].textContent).to.equal('Home Page');
+        expect(heading[0].textContent).to.equal('About Page');
 
         expect(paragraph.length).to.equal(1);
-        expect(paragraph[0].textContent).to.equal('Coffey was here');
+        expect(paragraph[0].textContent).to.equal('Learn more about us');
       });
 
       it('should have the expected <footer> tag from the dynamic app layout', function() {
diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/layouts/page.js b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/layouts/page.js
index bc19c555..bd743240 100644
--- a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/layouts/page.js
+++ b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/layouts/page.js
@@ -1,10 +1,12 @@
 export default class PageLayout extends HTMLElement {
   async connectedCallback() {
     this.innerHTML = `
-      <body>
-        <h2>Page Layout</h2>
-        <content-outlet></content-outlet>
-      </body>
+      <html>
+        <body>
+          <h2>Page Layout</h2>
+          <content-outlet></content-outlet>
+        </body>
+      </html>
     `;
   }
 }
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/about.md b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/about.md
new file mode 100644
index 00000000..3eb3f6d5
--- /dev/null
+++ b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/about.md
@@ -0,0 +1,3 @@
+### About Page
+
+Learn more about us
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/index.html b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/index.html
new file mode 100644
index 00000000..fafd9135
--- /dev/null
+++ b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/index.html
@@ -0,0 +1,5 @@
+
+<body>
+  <h3>Home Page</h3>
+  <p>Coffey was here</p>
+</body>
diff --git a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/index.md b/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/index.md
deleted file mode 100644
index 6f24cffb..00000000
--- a/packages/cli/test/cases/build.default.workspace-layouts-page-and-app-dynamic/src/pages/index.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Home Page
-
-Coffey was here
\ No newline at end of file