File in javascript – Angular 19

I have this snippet of code.. i want to know where i’m wrong.. i save an image, that i have stored inside my project, into a new File.. but when i try to reconvert it to an image i get an empty image..

function selectImagesToUpload() {
  const input = document.getElementById("product_pics");
  const preview = document.querySelector(".preview");
  const images = preview.querySelectorAll("div.preview > p");

  images.forEach(async item => {

    let fn = item.innerText.split(';')[0];
    let ext = fn.slice(fn.lastIndexOf('.') + 1, fn.length);

    let p = await fetch(fn).then(response => response.blob());
    let b = await p.arrayBuffer();
    let f0 = new File(new Uint8Array(b), fn, { type: `image/${ext}` });

    var reader = new FileReader();
    reader.onload = function (e) {
      var image = document.createElement("img");
      image.src = e.target.result;
      document.body.appendChild(image);
    }
    reader.readAsDataURL(f0);

  });
}

This is an example paragraph with the relative url (i set it in the assets of the project) : < p > img/prodotto7.jpg;< /p >

This is the result of the append to the dom : empty image

Can someone help me? This is like a test, i must do it work to go on with my project..

How to hide a div in form until selection is made

I need to hide a div within my (calculating) order form until a selection is made in a select menu.
I’ve scaled back this ‘Ice Cream Order Form’ example to see if anyone has any guidance.

In this example, I would want to hide the ‘toppings’ div until a selection is made from the ‘scoopsSelector’ select menu.

.plus {
  display: none;
}
<h1>Ice Cream Order Form</h1>

<form action="icecream_formHandler.php" method="post" enctype="multipart/form-data">

  <select id="flavor" name="flavor">

    <option selected="true" disabled="disabled">Select Your Flavor</option>
    <option value="Vanilla">Vanilla</option>
    <option value="Chocolate">Chocolate</option>
    <option value="Strawberry">Mixed</option>

  </select><br><br>

  <select id="scoopsSelector" name="scoopsSelector" onchange="calcuMath()">

    <option value='0' selected="true" disabled="disabled">How Many Scoops?</option>
    <option value="3">One Scoop - $3.00</option>
    <option value="5">Two Scoops - $5.00</option>
    <option value="7">Three Scoops - $7.00</option>

  </select><br><br>

  <!-- This hidden input uses it's population to pass the <select> text instead of the <select> 
    value to the php form handler  -->

  <input type="hidden" id="scoops" class="scoops" name="scoops">

  <script>
    const scoopsSelector = document.getElementById('scoopsSelector')
    scoopsSelector.addEventListener('input', function() {
      let selectedOptText = scoopsSelector.options[scoopsSelector.selectedIndex].text
      document.querySelector('.scoops').value = selectedOptText;
    })
  </script>



  <div name="toppings" id="toppings">


    <p>Toppings: (Optional)</p>

    <select id="plus" class="plus">
      <option value="add" id="add">+</option>
    </select>

    <input type="hidden" id="nuts" name="nuts" value="0" ><input type="checkbox"
    name="nutsCheckbox" onchange="this.previousSibling.value=3-this.previousSibling.value; 
    calcuMath();">&nbsp;Nuts $3.00<br><br>

    <select id="plus" class="plus">
      <option value="add" id="add">+</option>
    </select>

    <input type="hidden" id="sprinkles" name="sprinkles" value="0"><input type="checkbox"
    name="sprinklesCheckbox" onchange="this.previousSibling.value=3-this.previousSibling.value; 
    calcuMath();">&nbsp;Sprinkles $3.00<br><br>

    <select id="plus" class="plus">
      <option value="add" id="add">+</option>
    </select>

    <input type="hidden" id="syrup" name="syrup" value="0"><input type="checkbox"
    name="syrupCheckbox" onchange="this.previousSibling.value=4-this.previousSibling.value; 
    calcuMath();">&nbsp;Syrup $4.00<br><br><br>

    <select id="plus" class="plus">
      <option value="add" id="add">+</option>
    </select>


  </div>


  <select id="dishSelector" name="dishSelector" onchange="calcuMath()">

    <option value='0' selected="true" disabled="disabled">Cup or Cone</option>
    <option value="3">Cup - $3.00</option>
    <option value="4">Cone - $4.00</option>

  </select><br><br><br>



  <input type="hidden" id="dish" class="dish" name="dish">

  <script>
    const dishSelector = document.getElementById('dishSelector')
    dishSelector.addEventListener('input', function() {
      let selectedOptText = dishSelector.options[dishSelector.selectedIndex].text
      document.querySelector('.dish').value = selectedOptText;
    })
  </script>

  TOTAL: $<a id="result"></a><br><br>

  <script>
    function calcuMath() {
      var z;
      var a = document.getElementById("scoopsSelector").value;
      var b = document.getElementById("nuts").value;
      var c = document.getElementById("sprinkles").value;
      var d = document.getElementById("syrup").value;
      var e = document.getElementById("dishSelector").value;
      var sel = document.getElementById("plus");
      var selection = sel.options[sel.selectedIndex].value;
      var more = document.getElementById("add");
      if (selection == "add") {
        z = parseFloat(a) + parseFloat(b) + parseFloat(c) + parseFloat(d) + parseFloat(e);
      }

      document.getElementById("result").innerHTML = "" + z + "";
    }
  </script>

  <input type="submit" id="submitButton" name="submitButton" value="Place Order">

</form>

Seed data in Node.js

I’m working on a Node.js project using MongoDB, and I want to seed an admin user directly within my database connection file. My goal is to ensure that whenever the database connection is established, the admin user is seeded automatically if it doesn’t already exist
can you please help me and tell me is it correct?
Here is my db.js

const mongoose = require("mongoose");
const seedAdminUser = require("./seed.Admin");
dotenv.config();

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });

    console.log("Connected to MongoDB");
    
      await seedAdminUser();
  } catch (err) {
    console.error(" MongoDB connection error:", err.message);
    process.exit(1);
  }
};

module.exports = connectDB;

Here is my seedData

const bcrypt = require("bcryptjs");
const Admin = require("../models/admin.model");

const seedAdminUser = async () => {
  try {
    const adminEmail = process.env.ADMIN_EMAIL;
    const adminPassword = process.env.ADMIN_PASSWORD;

    // Check if admin exists
    const admin = await Admin.findOne({ email: adminEmail });

    if (admin) {
      console.log("Admin already exists");

      //Reactivate admin if disabled
      if (!admin.isActive) {
        admin.isActive = true;
        await admin.save();
        console.log("Admin reactivated!");
      }
      return;
    }

    // Hash Password
    const hashedPassword = await bcrypt.hash(adminPassword, 10);

    // Create Admin Admin
    await Admin.create({
      name: "Admin",
      email: adminEmail,
      password: hashedPassword,
      isActive: true,
    });

    console.log(" Admin user seeded successfully");
  } catch (err) {
    console.error(" Error seeding admin user:", err.message);
  }
};

module.exports = seedAdminUser;

so please tell me is it correct?If there is another way please tell me.

How to improve web camera streaming latency to v4l2loopback device with ffmpeg?

I’m trying to stream my iPhone camera to my PC on LAN.

What I’ve done:

  1. HTTP server with html page and streaming script:

    I use WebSockets here and maybe WebRTC is better choice but it seems like network latency is good enough

async function beginCameraStream() {
  const mediaStream = await navigator.mediaDevices.getUserMedia({
    video: { facingMode: "user" },
  });

  websocket = new WebSocket(SERVER_URL);

  websocket.onopen = () => {
    console.log("WS connected");

    const options = { mimeType: "video/mp4", videoBitsPerSecond: 1_000_000 };
    mediaRecorder = new MediaRecorder(mediaStream, options);

    mediaRecorder.ondataavailable = async (event) => {
      // to measure latency I prepend timestamp to the actual video bytes chunk
      const timestamp = Date.now();
      const timestampBuffer = new ArrayBuffer(8);
      const dataView = new DataView(timestampBuffer);
      dataView.setBigUint64(0, BigInt(timestamp), true);
      const data = await event.data.bytes();

      const result = new Uint8Array(data.byteLength + 8);
      result.set(new Uint8Array(timestampBuffer), 0);
      result.set(data, 8);

      websocket.send(result);
    };

    mediaRecorder.start(100); // Collect 100ms chunks
  };
}
  1. Server to process video chunks

import { serve } from "bun";
import { Readable } from "stream";

const V4L2LOOPBACK_DEVICE = "/dev/video10";

export const setupFFmpeg = (v4l2device) => {
  // prettier-ignore
  return spawn("ffmpeg", [
    '-i', 'pipe:0',           // Read from stdin
    '-pix_fmt', 'yuv420p',    // Pixel format
    '-r', '30',               // Target 30 fps
    '-f', 'v4l2',             // Output format
    v4l2device, // Output to v4l2loopback device
  ]);
};

export class FfmpegStream extends Readable {
  _read() {
    // This is called when the stream wants more data
    // We push data when we get chunks
  }
}

function main() {
  const ffmpeg = setupFFmpeg(V4L2LOOPBACK_DEVICE);
  serve({
    port: 8000,
    fetch(req, server) {
      if (server.upgrade(req)) {
        return; // Upgraded to WebSocket
      }
    },
    websocket: {
      open(ws) {
        console.log("Client connected");
        const stream = new FfmpegStream();
        stream.pipe(ffmpeg?.stdin);

        ws.data = {
          stream,
          received: 0,
        };
      },
      async message(ws, message) {
        const view = new DataView(message.buffer, 0, 8);
        const ts = Number(view.getBigUint64(0, true));
        ws.data.received += message.byteLength;
        const chunk = new Uint8Array(message.buffer, 8, message.byteLength - 8);

        ws.data.stream.push(chunk);

        console.log(
          [
            `latency: ${Date.now() - ts} ms`,
            `chunk: ${message.byteLength}`,
            `total: ${ws.data.received}`,
          ].join(" | "),
        );
      },
    },
  });
}

main();

After I try to open the v4l2loopback device

cvlc v4l2:///dev/video10

picture is delayed for at least 1.5 sec which is unacceptable for my project.

Thoughts:

  • Problem doesn’t seems to be with network latency

    latency: 140 ms | chunk: 661 Bytes | total: 661 Bytes
    latency: 206 ms | chunk: 16.76 KB | total: 17.41 KB
    latency: 141 ms | chunk: 11.28 KB | total: 28.68 KB
    latency: 141 ms | chunk: 13.05 KB | total: 41.74 KB
    latency: 199 ms | chunk: 11.39 KB | total: 53.13 KB
    latency: 141 ms | chunk: 16.94 KB | total: 70.07 KB
    latency: 139 ms | chunk: 12.67 KB | total: 82.74 KB
    latency: 142 ms | chunk: 13.14 KB | total: 95.88 KB
    

    ~150ms is actually too much for 15KB on LAN but there can some issue with my router

  • As far as I can tell it neither ties to ffmpeg throughput:

    Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pipe:0':
      Metadata:
        major_brand     : iso5
        minor_version   : 1
        compatible_brands: isomiso5hlsf
        creation_time   : 2025-03-09T17:16:49.000000Z
      Duration: 00:00:01.38, start:
    0.000000, bitrate: N/A
        Stream #0:0(und): Video: h264 (Baseline) (avc1 / 0x31637661), yuvj420p(pc), 1280x720, 4012 kb/s, 57.14 fps, 29.83 tbr, 600 tbn, 1200 tbc (default)
        Metadata:
          rotate          : 90
          creation_time   : 2025-03-09T17:16:49.000000Z
          handler_name    : Core Media Video
        Side data:
          displaymatrix: rotation of -90.00 degrees
    
    Stream mapping:
      Stream #0:0 -> #0:0 (h264 (native) -> rawvideo (native))
    
    [swscaler @ 0x55d8d0b83100] deprecated pixel format used, make sure you did set range correctly
    
    Output #0, video4linux2,v4l2, to '/dev/video10':
      Metadata:
        major_brand     : iso5
        minor_version   : 1
        compatible_brands: isomiso5hlsf
        encoder         : Lavf58.45.100
    
    Stream #0:0(und): Video: rawvideo (I420 / 0x30323449), yuv420p, 720x1280, q=2-31, 663552 kb/s, 60 fps, 60 tbn, 60 tbc (default)
        Metadata:
          encoder         : Lavc58.91.100 rawvideo
          creation_time   : 2025-03-09T17:16:49.000000Z
          handler_name    : Core Media Video
        Side data:
          displaymatrix: rotation of -0.00 degrees
    
    frame=   99 fps=0.0 q=-0.0 size=N/A time=00:00:01.65 bitrate=N/A dup=50 drop=0 speed=2.77x
    frame=  137 fps=114 q=-0.0 size=N/A time=00:00:02.28 bitrate=N/A dup=69 drop=0 speed=1.89x
    frame=  173 fps= 98 q=-0.0 size=N/A time=00:00:02.88 bitrate=N/A dup=87 drop=0 speed=1.63x
    frame=  210 fps= 86 q=-0.0 size=N/A time=00:00:03.50 bitrate=N/A dup=105 drop=0 speed=1.44x
    frame=  249 fps= 81 q=-0.0 size=N/A time=00:00:04.15 bitrate=N/A dup=125 drop=0 speed=1.36
    frame=  279 fps= 78 q=-0.0 size=N/A time=00:00:04.65 bitrate=N/A dup=139 drop=0 speed=1.31x
    
  • I also tried to write the video stream directly to video.mp4 file and immediately open it with vlc but it only can be successfully opened after ~1.5 sec.

  • I’ve tried to use OBS v4l2 input source instead of vlc but the latency is the same

Angular component is not showing up(or rendering) in the elements

I am new to the Angular JS , and I have been trying to make a ecommerce website in the Angular. My component

<app-products-list/>

is not showing up in the elements of the browser.

app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { HeaderComponent } from './components/header/header.component';
import { ProductsListComponent } from "./pages/products-list/products-list.component";

@Component({
  selector: 'app-root',
  imports: [HeaderComponent, ProductsListComponent],
  template: `
   <app-header>

   <app-products-list/>
  `,
  styles: [],
})
export class AppComponent {
  title = 'angular-ecomm';
}

products-list.component.ts

import { Component, signal } from '@angular/core';
import { Product } from '../../models/products.model';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-products-list',
  standalone: true,
  imports: [CommonModule],
  template: `
   <div class="p-8 grid grid-cols-2 gap-4">
      @for (product of products(); track product.id) {
        {{product.title}}
      }
    </div>
  `,
  styles: ``
})
export class ProductsListComponent {
  products = signal<Product[]>([
    {
      id: 1,
      title: 'Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops',
      price: 109.95,
      image: 'https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg',
      stock: 10,
    },
    {
      id: 2,
      title: 'Mens Casual Premium Slim Fit T-Shirts',
      price: 22.3,
      image: 'https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg',
      stock: 100,
    },
    {
      id: 3,
      title: 'Mens Cotton Jacket',
      price: 55.99,
      image: 'https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg',
      stock: 0,
    },
    {
      id: 4,
      title: 'Mens Casual Slim Fit',
      price: 15.99,
      image: 'https://fakestoreapi.com/img/71YXzeOuslL._AC_UY879_.jpg',
      stock: 5,
    },
    {
      id: 5,
      title: "John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet",
      price: 695,
      image: 'https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg',
      stock: 1,
    }
  ]); 

  constructor() {
    console.log("✅ ProductsListComponent Loaded!");
  }
}

products.model.ts

export interface Product{
    id: number;
    title: string; 
    image: string; 
    price: number; 
     stock?: number;  
}

I first thought it is the issue with taliwind CSS, but then when I inspect I saw that

<app-products-list/>

is not showing up in the elements.

enter image description here

I tried ng-serve again and again but didnt work. I have also tried to use ngFor loop instead of For but it results in all the same. I have also get rid of the CSS but that is not working too. I have added a function at the end of products-list.component.ts and saw it is seen in the console but the <app-products-list/> is not rendering.

constructor() {
    console.log("✅ ProductsListComponent Loaded!");
  }

Update Collision Of Skinned Mesh In ThreeJS

In my game I have placed a door (Skinned Mesh) in an open doorway. I can activate the opening animation of the door with a switch on the wall. The animation basically just moves the door to the left a bit. However the collison does not move with the animation and I’m not able to go through the now open doorway.

I’m using this code to give all my meshes collision:

const worldOctree = new Octree()

if(child.isMesh) {
    worldOctree.fromGraphNode(child)
}

As I said when the animation plays the collison box stays at the same place and I cannot go through the open doorway.

I thought using the mesh itself as paramater in the fromGraphNode instead of the gltf.scene would solve the problem but it didn’t. I came across this hack here but this is from 2018 and I’m not sure if it is still relevant.

How can I get the collision box of my model to update correctly?

Javascript Date Swap

I am writing up some code that is supposed to take two strings, convert them to dates, then swap them if that start date is before the end date.

Issue I am running into is in the scenario below it is flipping them despite 2025-01-31 being before 2025-02-03…

Does anyone see something that I am missing here?

var start = "20250131190700";
var end = "20250203143400";

if(new Date(start.substring(0,4), start.substring(4,6), start.substring(6,8), start.substring(8,10), start.substring(10,12), start.substring(12,14)) > new Date(end.substring(0,4), end.substring(4,6), end.substring(6,8), end.substring(8,10), end.substring(10,12), end.substring(12,14))){
    var result = "swap"
}
else{
    var result = "dont swap"
}

discord app (built with discord.js, Node.js) slash commands work inside server but not in DM

I’m currently building a Discord app that allows me to view a user’s status (online, DND, offline) and what songs they are listening to. This works perfectly in my own server:

My app works in the server

However, when I switch to DMs, none of my commands (activity, ping, user) show up:

Commands are missing in DMs

My project directory is structured as follows:

commands/
  └── utility/
        ├── ping.js
        ├── check_activity.js
        ├── user.js
server/
  ├── api.js
  ├── models/
        └── activity.js
deploy-commands.js
index.js

The commands work fine in a server but do not function in DMs, and I can’t figure out why.

index.js (excluding MongoDB-related parts)

const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js');
const { token } = require('./config.json');

const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
        GatewayIntentBits.GuildMembers,
        GatewayIntentBits.GuildPresences,
    ],
});

client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);

for (const folder of commandFolders) {
    const commandsPath = path.join(foldersPath, folder);
    const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
    for (const file of commandFiles) {
        const filePath = path.join(commandsPath, file);
        const command = require(filePath);
        if ('data' in command && 'execute' in command) {
            client.commands.set(command.data.name, command);
        } else {
            console.warn(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
        }
    }
}

client.once(Events.ClientReady, readyClient => {
    console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});

client.on(Events.InteractionCreate, async interaction => {
    if (!interaction.isChatInputCommand()) return;
    const command = interaction.client.commands.get(interaction.commandName);

    if (!command) {
        console.error(`No command matching ${interaction.commandName} was found.`);
        return;
    }

    try {
        await command.execute(interaction);
    } catch (error) {
        console.error(error);
        const errorMessage = 'There was an error while executing this command!';
        if (interaction.replied || interaction.deferred) {
            await interaction.followUp({ content: errorMessage, flags: MessageFlags.Ephemeral });
        } else {
            await interaction.reply({ content: errorMessage, flags: MessageFlags.Ephemeral });
        }
    }
});

client.login(token);

I have tried:

  • Searching online for similar issues, but I couldn’t find anything useful.
  • Checking if I need different permissions or intents for DMs.

Is this happening because my bot relies on “guild”-related functions, which do not work in DMs? If so, what should I change to support both servers and DMs?

I’m new to JavaScript and Discord app development, so any help would be greatly appreciated! Thanks!

How can I append each array item in JavaScript?

I was trying to do some DOM by displaying a text using a for loop from JavaScript to HTML but it keeps bouncing off the other words and settles on the last word in the loop. What am I doing wrong?

let sentence = ["Hello ", "my ", "name ", "is ", "Shey"] 
let greetingEl = document.getElementById("greeting-el")

// Render the sentence in the greetingEl paragraph using a for loop and .textContent

for (let i = 0; i < sentence.length; i+=1){
    greetingEl.textContent = sentence[i] 
}

Why does click handler not repeat? [duplicate]

3 click handlers in a loop stop clicking each other, why? Shouldn’t they keep clicking in a loop forever?

<div id="a">a</div>
<div id="b">b</div>
<div id="c">c</div>
<script id="script-a">document.getElementById("a").onclick = function(){ console.log("a"); document.getElementById("b").click(); }</script>
<script id="script-b">document.getElementById("b").onclick = function(){ console.log("b"); document.getElementById("c").click(); }</script>
<script id="script-c">document.getElementById("c").onclick = function(){ console.log("c"); document.getElementById("a").click(); }</script>

Clicking a prints out a, b, c, a, but then it stops
Why does it stop and not click handler a continues to click b again?

Image to blob – Blob to image – in Javascript or Typescript mode (Angular 19)

This is how i convert an image to a Blob in a javascript script in angular

function getImagesForUpload() {
  const preview = document.querySelector(".preview");
  const images = preview.querySelector("div.preview > p");

  if (images) {
    console.log(images);
    console.log(images.innerText.split(';'));
    let arr = images.innerText.split(';');
    arr.pop();
    let blobs = new Array();
    arr.forEach((item, i, arr) => {
      let file = item;
      let image = item.split('.');
      console.log(image);
      var blob = new Blob([file], { type: `image/${image[1]}` });
      let imageSrc = URL.createObjectURL(blob);
      blobs.push(imageSrc);
    });
    return [arr, blobs]
  } else
    return [null, null];
}

This is what i want to store returning an
destructured array

How can i get the image back having saved the ‘name.extension’ and the url of the ‘blob’ in javascript or typescript?

I’m coding web site for collecting opinions [closed]

I did git bash setting and new web service in RENDER
I posted site in netlify
I think that my code isn’t complete but I don’t what is wrong
It is possible to click “제출”(submit button) button and It is possible to click option button
but It isn’t possible to click “확인”(check button) button(do not respond to anything) in “관리자 확인”(admin checking button) button
This site url is that
https://sjdshs-2-7.netlify.app/
and this picture is file of my web site deploy production
enter image description here
if you require code, I will send code
(I’m korean that don’t use well english/if you feel strange my text, I’m very sorry)

I sign up github, render, netlify
I fight git bash options beacause I don’t register my folder file(feedback_project) in git bash, during 3 hour is to reserch solution for resolving error
I modify code many time(originally this code isn’t checking submit button, option button And so on)

Merge hooks in QUnit

I need to reuse sets of setup and cleanup logic in multiple QUnit modules without code duplication. These pieces of logic are already encoded as “module hook” objects as per the QUnit API. Some of the hooks might be async. Here is an example – The content of these hooks doesn’t really mean anything, it’s just for demonstration.

const fruitHook = {
  async before () {
    this.banana = { ripeness: await generateRandomInitialRipeness() };
    this.apple = { ripeness: await generateRandomInitialRipeness() };
  },

  beforeEach () {
    this.banana.ripeness += 1;
    this.apple.ripeness += 1;
  }
};

const nutHook = {
  before () {
    this.walnut = { cracked: false };
    this.peanut = { cracked: false };
  }
};

I would like to combine these hooks in different ways as needed by my QUnit modules.

// This module needs the fruitHook
QUnit.module('TestFruits', fruitHook);

// This module needs the nutHook
QUnit.module('TestNuts', nutHook);

// This module needs both
QUnit.module('TestFruitsAndNuts', /* ??? */);

In a real use case, I would have a larger set of hooks that need to be combined into various different subsets. Does QUnit offer a way to combine these hooks?

I can write my own “hooks concatenator” utility:

function concatenateHooks (hooks) {
  const resultingHook = {
    async before () {
      for (const hook of hooks) {
        if (typeof hook.before === 'function') {
          hook.before && await hook.before.call(this);
        }
      }
    },

    async beforeEach () { /* ... */ }

    async afterEach () { /* ... */ }

    async after () { /* ... */ }
  }
}

And use it like so:

QUnit.module('TestFruitsAndNuts', concatenateHooks([fruitHook, nutHook]);

In my implementation I’m forcing all hooks to become async, though I suspect I could make it a bit more elaborate to avoid awaiting if all passed hooks are synchronous.

Is there a feature in QUnit that does this and that I’ve missed?