Click doesn’t respond on iOS

The click event doesn’t fire on iOS with the following JavaScript. It works fine on Windows. Why is this happening? Is there a workaround?

const pin = new google.maps.marker.PinElement({
    scale: 1.0,
    glyphColor: "green"
});
var testMarker = new google.maps.marker.AdvancedMarkerElement({
    content:        pin.element,
    gmpClickable:   true,
    gmpDraggable:   true
});
testMarker.position = new google.maps.LatLng(
    34.718420, 135.556109
);
testMarker.map = mapObj;
testMarker.addListener(
    'dragend',
    function (event) {
        alert('dragged');
    }
);
testMarker.addListener(
    'click',
    function (event) {
        alert('clicked');
    }
);

On Windows + Chrome, the “clicked” alert is displayed correctly, but on iOS + Chrome (or Safari), nothing happens. Regarding “dragend”, it works as expected in both cases. Using “gmp-click” instead of the “click” event produces the same results. Also, if you don’t add the “dragend” event, the click event works correctly.

How to add an item to the cart only once?

I am working on my store project using JS. I have the addToCard code to add a product to the cart. This code works, but I would like to add the product only once. After which, when I click the button(add-to-cart) again, there was a message – This product is already in the cart. For network requests, I use the Axios library along with Firebase services(Realtime Database, Storage)

export class HomePage extends Component {
  constructor() {
    super();
    this.state = {
      
      orderCart: [],
    };
  }
 
  addToCard = (e) => {
    if (e.target.closest(".add-to-cart")) {
      
      let id = e.target.parentElement.parentElement.dataset.id;
      let price = e.target.parentElement.parentElement.dataset.price;
      let name = e.target.parentElement.parentElement.parentElement.dataset.name;
      let img = e.target.parentElement.parentElement.parentElement.dataset.img;
      
      const cartItems = { id, price, name, img };

      apiService.post("/order", cartItems).then(() => {
       this.setState({
          ...this.state,
          orderCart: this.state.orderCart?.concat(cartItems),
        })
      })
      console.log(id, cartItems);
      
      useToastNotification({
        message: "Product in the cart!",
        type: TOAST_TYPE.success,
      });
    }
  };
  
  componentDidMount() {
    this.addEventListener("click", this.addToCard);
  }

  componentWillUnmount() {
    this.removeEventListener("click", this.addToCard);
  }
}

customElements.define('home-page', HomePage);

For page navigation and for the product card, I use Handlebars. Here is my product card:

{{#each products}}
      <div data-id="{{this.id}}" class="w-full md:w-1/3 xl:w-1/4 p-6 flex flex-col">
        
        <div
          data-img="{{this.img}}"
          data-name="{{this.name}}"
        >
          <img class="hover:grow hover:shadow-lg" src="{{this.img}}" alt="product" />
          <p data-name="{{this.name}}">{{this.name}}</p>
          <div 
            data-id="{{this.id}}"
            data-img="{{this.img}}" 
            data-name="{{this.name}}"
            data-price="{{this.price}}" class="pt-3 flex items-center justify-between">
            <p data-price="{{this.price}}" class="pt-1 text-gray-900">{{this.price}}</p>

            <button
             class="add-to-cart"
             data-id="{{this.id}}"
             data-img="{{this.img}}" 
             data-name="{{this.name}}"
             data-price="{{this.price}}">
             <svg class="h-6 w-6 fill-current text-gray-500 hover:text-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
               <path d="M12,4.595c-1.104-1.006-2.512-1.558-3.996-1.558c-1.578,0-3.072,0.623-4.213,1.758c-2.353,2.363-2.352,6.059,0.002,8.412 l7.332,7.332c0.17,0.299,0.498,0.492,0.875,0.492c0.322,0,0.609-0.163,0.792-0.409l7.415-7.415 c2.354-2.354,2.354-6.049-0.002-8.416c-1.137-1.131-2.631-1.754-4.209-1.754C14.513,3.037,13.104,3.589,12,4.595z M18.791,6.205 c1.563,1.571,1.564,4.025,0.002,5.588L12,18.586l-6.793-6.793C3.645,10.23,3.646,7.776,5.205,6.209 c0.76-0.756,1.754-1.172,2.799-1.172s2.035,0.416,2.789,1.17l0.5,0.5c0.391,0.391,1.023,0.391,1.414,0l0.5-0.5 C14.719,4.698,17.281,4.702,18.791,6.205z" />
             </svg>
            </button>
          </div>
        </div>
      </div>
    {{/each}}

I don’t understand how to implement adding a product via ID only once, with this addToCard code

How to log certain Form Responses

I am using the code below from Apps Script ItemResponse documentation. It is triggered by a form response. The trigger and code are working fine, but I only want to log the most recent form response, and I only want to log the responses to certain items only (items 1 through 3). How can I alter the code to do this?

// Open a form by ID and log the responses to each question.
const form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
const formResponses = form.getResponses();
for (let i = 0; i < formResponses.length; i++) {
  const formResponse = formResponses[i];
  const itemResponses = formResponse.getItemResponses();
  for (let j = 0; j < itemResponses.length; j++) {
    const itemResponse = itemResponses[j];
    Logger.log(
        'Response #%s to the question "%s" was "%s"',
        (i + 1).toString(),
        itemResponse.getItem().getTitle(),
        itemResponse.getResponse(),
    );
  }
}

I tried the following code with ran successfully, but nothing was logged. Additionally I’m not sure how to specify I only want responses for items 1 through 3.

  var formResponses = form.getResponses();
  var lastResponse = formResponses[formResponses.length - 1];

  const itemResponses = lastResponse.getItemResponses();
  for {
    const itemResponse = itemResponses[1:3];
    Logger.log(
        'Response #%s to the question "%s" was "%s"',
        (i + 1).toString(),
        itemResponse.getItem().getTitle(),
        itemResponse.getResponse(),
    );
  }

How to fix the Facebook’s “Advertiser Tracking Enabled parameter volume out of range” error

My team wants to run an ad campaign, but the event which we wanna use is not eligible for it, as it says “Advertiser Tracking Enabled parameter volume out of range (Your app needs a certain volume of events with the Advertiser Tracking Enabled (ATE) parameter set as false or no.)”.

We are using appsflyer to send the event from appsflyer to facebook. From server we are sending event to appsflyer then mapping same event to facebook.

Appsflyer says that they pass the ATE internally based on the value of ATT.

In order to fix the issue we are now passing
att , idfa & idva in the event.

Still the event is not yet eligible for the ad.

We followed this doc for the appsflyer event.
https://dev.appsflyer.com/hc/reference/s2s-events-api3-post

Code:


  export const ATT_STATUS = {
   NOT_DETERMINED: 0,
   RESTRICTED: 1,
   DENIED: 2,
   AUTHORIZED: 3,
  };

  const IDFA_VALUES = {
  RESTRICTED: '00000000-0000-0000-0000-000000000000',
  NOT_AVAILABLE: 'Not Available / No Consent',
};

// Determine ATT status based on IDFA value
export const getAttStatus = (idfa) => {
  if (!idfa) return ATT_STATUS.NOT_DETERMINED;

  if (idfa === IDFA_VALUES.RESTRICTED) return ATT_STATUS.RESTRICTED;
  if (idfa === IDFA_VALUES.NOT_AVAILABLE) return ATT_STATUS.DENIED;

  return ATT_STATUS.AUTHORIZED;
};

  async function sendEvent(eventName, customer) {
  try {
    const headers = {
      'Content-Type': 'application/json',
      authentication: configs.appsflyer.apiAccessToken,
    };

    const { metaData } = customer;

    const payload = {
      att: ATT_STATUS.NOT_DETERMINED, // Default value
      eventName,
      customer_user_id: customer?.id,
      appsflyer_id: customer?.appsflyerId,
      eventTime: moment().utc().format('YYYY-MM-DD HH:mm:ss.SSS'),
      bundleIdentifier: configs.appsflyer.bundleIdentifier,
    };

    // Set IP address (prioritize metaData.ipv4 over customer.ip_address)
    if (metaData?.ipv4) {
      payload.ip = metaData.ipv4;
    } else if (customer?.ip_address) {
      payload.ip = customer.ip_address;
    }

    // Process metaData if available
    if (metaData && typeof metaData === 'object') {
      const idfa = metaData.IDFA;

      if (idfa) {
        payload.idfa = idfa;
        payload.att = getAttStatus(idfa);
      }

      // Add IDFV if available
      if (metaData.IDFV) {
        payload.idfv = metaData.IDFV;
      }
    }

    const response = await axios.post(
      `${configs.appsflyer.apiHost}/inappevent/${configs.appsflyer.appId}`,
      payload,
      { headers }
    );
    return response.data;
  } catch (error) {
    logError(error);
  }
}

After updating the code we have sent around 50+ events, still the event is not eligible, also waited 24hrs still no update.

Has anyone faced this issue? Or does anyone knows how to fix it? Facebook don’t even provide the number of event it needs to track to make it eligible.
Thanks.

TypeError ‘vega-util’ does not resolve to valid URL

I am new to Javascript, Vega-lite, and Rollup. I have been able to solve some errors and unresolved dependencies. I am getting an error in the browser. I do not know what is going on in the bundle js file in the browser.

I am working to create a template or an intial Vega-lite data visualization. This is from the vizhub’s “Fork for Vega-lite api template”.

https://vizhub.com/Dalborg/f5ab04b76aa3483da70c4b58bb635fe7

The error message states: TypeError: Module name, ‘vega-util’ does not resolve to a valid URL.

I have looked up javascript and my the error, but I have not found something that I can deceipher to be relevant to my particular type of error.

I have fixed the circular dependencies by defining some variables for the different vega-lite plugins in the external and plugins (fields?). See the first code block below.

//rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import nodeResolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import commonjs from '@rollup/plugin-commonjs';
import { babel } from '@rollup/plugin-babel';
import nodePolyfills from 'rollup-plugin-polyfill-node';

export default {
    // onwarn: function ( message ) {
    // if (message.code === 'CIRCULAR_DEPENDENCY') {
    //   return;
    // }
    // console.error(message);
    // },
    input:'./main.js',
    output: {
      file:'build/bundle.js',
      format:'iife',
      name: 'MyReactApp',
      globals: {
        'd3': 'd3',
        'vega-embed': 'vega-embed',
        'vega': 'vega',
        'vega-lite': 'vega-lite',
        'vl': 'vl',
        'vega-lite-api': 'vega-lite-api'
      }
    },
    external: [
      'd3',
      'vega-embed',
      'vega-lite',
      'vega-lite-api',
      'vega'
    ],
    plugins: [
      resolve(),
      nodeResolve({
          jsnext:true,
          main:true,
      }),
      replace({
        'process.env.NODE_ENV': JSON.stringify('development'),
        preventAssignment: true,
      }),
      babel({
        babelHelpers: 'bundled',
        extensions: ['.js', '.jsx']
      }),
      commonjs({
        include: 'node_modules/**',
        requireReturnsDefault: 'auto'
      }),
      nodePolyfills(),
    ]
};

This is the second code block retrieved from source plugin in my safari browser.

import { isArray, isString, isObject } from 'vega-util';

var version$1 = "1.0.0";
var pkg = {
    version: version$1};

/**
 * Format the value to be shown in the tooltip.
 *
 * @param value The value to show in the tooltip.
 * @param valueToHtml Function to convert a single cell value to an HTML string
 */
function formatValue(value, valueToHtml, maxDepth, baseURL) {
    if (isArray(value)) {
        return `[${value.map((v) => valueToHtml(isString(v) ? v : stringify(v, maxDepth))).join(', ')}]`;
    }
    if (isObject(value)) {
        let content = '';
        const { title, image, ...rest } = value;
        if (title) {
            content += `<h2>${valueToHtml(title)}</h2>`;
        }
        if (image) {
            content += `<img src="${new URL(valueToHtml(image), baseURL || location.href).href}">`;
        }
        const keys = Object.keys(rest);
        if (keys.length > 0) {
            content += '<table>';
            for (const key of keys) {
                let val = rest[key];
                // ignore undefined properties
                if (val === undefined) {
                    continue;
                }
                if (isObject(val)) {
                    val = stringify(val, maxDepth);
                }
                content += `<tr><td class="key">${valueToHtml(key)}</td><td class="value">${valueToHtml(val)}</td></tr>`;
            }
            content += `</table>`;
        }
        return content || '{}'; // show empty object if there are no properties
    }
    return valueToHtml(value);
}
function replacer(maxDepth) {
    const stack = [];
    return function (key, value) {
        if (typeof value !== 'object' || value === null) {
            return value;
        }
        const pos = stack.indexOf(this) + 1;
        stack.length = pos;
        if (stack.length > maxDepth) {
            return '[Object]';
        }
        if (stack.indexOf(value) >= 0) {
            return '[Circular]';
        }
        stack.push(value);
        return value;
    };
}

I can’t find why nth iterations of an image are not loading

Hi I am trying to create a timeline for my fantasy world, and it works for the most part. You can see it in action here. https://www.skazkaworld.com/timeline.html

As you can see, the background rune image from the 8th entry and beyond does not display correctly. I cannot for the life of me figure out what the issue is. I’m still learning so my code is not very elegant.

Relevant HTML:

<body>
      <div id="navbar"></div><!-- Navbar will be loaded here -->
      <div class="timeline" id="timeline"></div>
    <script src="main.js"></script>
    <script src="timeline.js"></script>
    <script>
        // Load navbar.html dynamically        
        fetch("navbar.html")
            .then(response => response.text())
            .then(data => {
                document.getElementById("navbar").innerHTML = data;

                // Now that navbar is loaded, attach hamburger event
                const hamburger = document.querySelector(".hamburger");
                const navLinks = document.querySelector(".nav-links");

                if (hamburger && navLinks) {
                    hamburger.addEventListener("click", () => {
                        navLinks.classList.toggle("active");
                        hamburger.classList.toggle("open");
                    });
                }
            });
    </script> 
  </body>

CSS:

/* Timeline */ .timeline { 
  display: block; 
  margin: 100px auto 20px; 
  width: 800px; 
  padding: 14px; 
  background: rgba(20, 20, 20, 0.85); 
  height: 80vh; 
  overflow: auto;
  background-image: url("assets/timeline.jpg"); /* replace with your image */ 
  background-repeat: repeat-y; /* repeats vertically */ 
  background-position: center top; /* centers it horizontally */ 
  background-size:cover; /* adjust if needed, e.g., "contain" or specific width */ 
}

.event { 
  margin: 18px 0; 
  cursor: pointer; 

} .event-header { 
  display: flex; 
  align-items: center; 
}

.rune { 
  flex-shrink: 0; 
  width: 28px; 
  height: 28px; 
  margin-right: 8px; 
  background: url("assets/rune-marker.png") center/contain no-repeat; 
  filter: grayscale(100%) brightness(0.9); 
  transition: filter 0.3s ease, transform 0.35s ease; 
}

.rune.active { filter: grayscale(0%) brightness(1.1); 
  transform: scale(1.12); }

.event-details { 
  max-height: 0; 
  overflow: hidden; 
  opacity: 0; 
  transition: max-height 0.4s ease, opacity 0.4s ease; 
  padding-left: 36px; 
  font-size: 0.9em; 
  color: #ccc; 
}

.event.open .event-details { 
  max-height: 200px; 
  opacity: 1; 
  margin-top: 4px; 
}

Finally the JS:

// timeline.js
document.addEventListener("DOMContentLoaded", () => {
  const timeline = document.getElementById("timeline");

  // Timeline data array
  const timelineEvents = [
    {
      year: "1200 AE",
      title: "Founding of the First Ascent",
      details: "The First Ascent arises atop the frozen peaks of Skazka, where mortals defied the giants."
    },
    {
      year: "1345 AE",
      title: "The Rift of Molach",
      details: "A tear in the weave of reality as Molach's fall tore open the land and spirits slipped through."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    // Add more events here...
  ];

  // Generate events dynamically
  timelineEvents.forEach((ev, i) => {
    const eventDiv = document.createElement("div");
    eventDiv.className = "event";
    eventDiv.dataset.id = i;

    eventDiv.innerHTML = `
      <div class="event-header">
        <span class="rune"></span>
        <div>
          <strong>${ev.year}</strong><br>
          ${ev.title}
        </div>
      </div>
      <div class="event-details">
        ${ev.details}
      </div>
    `;

    timeline.appendChild(eventDiv);
  });

  // Scroll rune activation - FIXED to use timeline container scroll
  const events = document.querySelectorAll(".event");
  
  function onTimelineScroll() {
    const timelineRect = timeline.getBoundingClientRect();
    const scrollTop = timeline.scrollTop;
    const viewportMiddle = timelineRect.height / 2;
    
    events.forEach(evt => {
      const eventRect = evt.getBoundingClientRect();
      const timelineTop = timelineRect.top;
      const eventTop = eventRect.top - timelineTop + scrollTop;
      const rune = evt.querySelector(".rune");
      
      // Activate rune if event is in upper half of timeline viewport
      if (eventTop < scrollTop + viewportMiddle) {
        rune.classList.add("active");
      } else {
        rune.classList.remove("active");
      }
    });
  }

  // Toggle event details on click
  events.forEach(evt => {
    evt.addEventListener("click", () => {
      evt.classList.toggle("open");
    });
  });

  // Listen to timeline scroll instead of window scroll
  timeline.addEventListener("scroll", onTimelineScroll);
  onTimelineScroll(); // Initial call
});

testing an useState change inside a callback

I have a component like this to test:

import {doSomethingMocked} form 'mockedLibrary';

const TheComponent = (props) => {
  const [isToTest, setIsToTest] = useState(false);
  const handleClick = useCallback(()=>{
    if(isToTest) return;
    setIsToTest(true);
    doSomethingMocked();
  }, [isToTest]);
  return (
    <button onClick={handleClick}>Click me</button>
  );
}

The Unit Test is something like this

import {render, fireEvent, act} form '@testing-library/react';
import {doSomethingMocked} form 'mockedLibrary';
jest.mock('mockedLibrary');

describe('The Component', ()=>{
  beforeEach(async ()=>{
    const sut = render(<MyProvider value={{}}><TheComponent/></MyProvider>);
    await act(async()=> await fireEvent.click(sut.getByText('Click me'))); // in theory the act now is unnecesary, any way still the state dows not toggle
    await act(async()=> await fireEvent.click(sut.getByText('Click me')));
  });
  it('Should only doSomething once', sync ()=>{
    expect(doSomethingMocked).toHaveBeenCalledTimes(1); // but it's called twice
  })
})

How can I test the boolean useState toggles?

I already run the component and works fine, but test wise, the useState change never happens.
Previously with enzyme after the wraper.update() was enought to test it.

Karma + Jasmin mock query param

I am new to ts and to frontend. I want to write test that mocks query param in uri.

it('should read the query parameter', () => {
  const stub = sinon.stub(window.location, 'search').get(() => '?query=test');

  const getQueryParam = () => {
    return new URLSearchParams(window.location.search).get('query');
  };

  const query = getQueryParam();

  assert.strictEqual(query, 'test');

  stub.restore();
});

I have googled that sinon can help me with this but i get: TypeError: Cannot redefine property: search. My intuition says that it shouldn’t be so complicated. Any best practices to accomplish it?

How can I prevent the second digit from shifting when deleting the first digit when editing a day in a date imask?

I am using IMaskJS. When you delete the first digit of the day of the date, the second one shifts to the left and takes its place, please tell me how to prevent this behavior.

{
          mask: Date,
          pattern: '`DD.`MM.`YYYY',
          format: date => moment(date).format('DD.MM.YYYY'),
          parse: str => moment(str, 'DD.MM.YYYY'),
          lazy: false,
          blocks: {
            DD: {
              mask: IMask.MaskedRange,
              from: 1,
              to: 31
            },
            MM: {
              mask: IMask.MaskedRange,
              from: 1,
              to: 12
            },
            YYYY: {
              mask: IMask.MaskedRange,
              from: 1000,
              to: 2999
            }
          }
        }

I tried to change the pattern field in the imask settings. Correct behavior is expected without shifting the numbers.

How to send a print command from a web application to a Bluetooth Brother label printer?

I am developing a mobile web application (React/JavaScript) that needs to send print commands to a Brother label printer (model TD-2120N / QL series) connected via Bluetooth.

  1. Using the browser’s window.print() – not suitable since it opens the system dialog and doesn’t support direct label formatting.

  2. Checking Brother’s SDK – seems focused on native apps (Android/iOS/Windows), not web.

  3. Looking into Web Bluetooth API – limited documentation on integrating with Brother printers.

Goal

Allow users to print labels (text + barcodes) directly from a mobile web app to a Bluetooth-connected Brother label printer without installing a native app..

Questions

  1. Can the Web Bluetooth API be used on mobile browsers (Chrome/Android or Safari/iOS) to connect and send print commands to Brother printers?

  2. Is a bridge app or service (like a small native companion app) required?

  3. Any JavaScript examples of successfully sending label data to a Brother printer over Bluetooth?

Meshes not loading in

In my game ive updated the rendering to be more optimized but that led to some errors and not nothing is loading, when checking the f12 menu it shows this and i have no idea what it means

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'mergeBufferGeometries')
    at greedyMesh ((index):570:52)
    at rebuildWorldMesh ((index):619:16)
    at Socket.<anonymous> ((index):779:3)
    at Emitter.emit (index.js:136:20)
    at Socket.emitEvent (socket.js:553:20)
    at Socket.onevent (socket.js:540:18)
    at Socket.onpacket (socket.js:508:22)
    at Emitter.emit (index.js:136:20)
    at manager.js:217:18

Heres the code

Ive tried searching it up and at first i thought it was something with the buffer geometries utils but i tried changing it and it didnt work

Writing tests in Jasmine for functions with dependancy on data from remote host whithout making actual fetch request

I apologize beforehand for this post, but I am struggling to figure out how to mock data from fetch request without making request itself and then test functions which have this data as dependency in Jasmine.

Here is my JavaScript code:

let products = []; //Array where transformed data from fetch request is stored for further use

function getProduct(productId) {//function to get product with certain id

  let matchProduct = products.find(product => product.id === productId)

  return matchProduct;

}


class Product { /*Example of classes used in function below, I can test them fine and they pass tests*/

  id;
  image;
  name;
  rating;
  priceCents;
  keywords;

  constructor(productDetails) {

    this.id = productDetails.id;
    this.image = productDetails.image;
    this.name = productDetails.name;
    this.rating = productDetails.rating;
    this.priceCents = productDetails.priceCents;
    this.keywords = productDetails.keywords;

  }

  getStars() {
    return `images/ratings/rating-${this.rating.stars * 10}.png`;
  }

  getPrice() {
    return `$${formatCurrency(this.priceCents)}`;
  }

  extraInfoHTML() {
    return ''
  }
}



function loadProducts() {

  const promise = fetch('link to host').then(response => {
    
    return response.json(); //host responds with array of objects if translated in json

  }).then(productData => { /*manipulating received array of objects to transform objects into classes and saving in new array*/

    products = productData.map(productDetails => {

      if (productDetails.keywords.includes('appliances')) {

        productDetails.type = 'appliance';
        productDetails.instructionLink = 'images/appliance-instructions.png';
        productDetails.warrantyLink = 'images/appliance-warranty.png';

        return new Appliance(productDetails);

      }

      if (productDetails.type === 'clothing') {

        return new Clothing(productDetails);

      }

      return new Product(productDetails);

    });

  }).catch(error => {

    console.error(error.message);

  });

  return promise;
}

All code works well and does what it does outside of test environment, but I got interested in how to test function getProduct() without making actual fetch request to remote host and how to test if remote host responded with correct data on request itself and/or request used correct link using Jasmine.

How can I reliably detect and apply only user-edited text changes to the original static HTML?

I’m building a tool that allows users to edit text directly on a live webpage. The goal is to let users change only the visible text content (e.g., in <p>, <h1>, <span>, etc.), and then apply those changes to the original static HTML structure — without affecting scripts or dynamic content.

What I’ve implemented so far:
1.Fetch original HTML via fetch(window.location.href) and parse it using DOMParser.
2.Enable inline editing by setting contentEditable=true on certain elements (h1–h6, p, div, span).
3.Compare the original and live DOMs, and if a text node has changed, replace it in the original DOM.
4.Export the updated original DOM as static HTML for download.

Even though users only change a few pieces of text, my script ends up modifying more text nodes than expected — even ones that the user did not change.

What I’m looking for:

A reliable way to compare and update only user-modified text content.
A method to ignore false positives caused by whitespace, formatting, or DOM reflows.
An approach that works even when parts of the page are rendered dynamically but should be excluded from export.

Any suggestions to improve text diffing accuracy, DOM mapping strategies, or handling dynamic vs static content separation would be very helpful.

Thank you!

gradlew build – FAILURE: Build failed with an exception

Unable to launch Gradlew build. I’ve tried everything, but it won’t launch. It works fine, but as soon as I want to create a mod for Minecraft, it doesn’t work.
I’m attaching a Google Drive link with all the configurations.

https://drive.google.com/drive/folders/1aauhnYdlnvQjEY7ppI938XknCmpWwSlI?usp=drive_link

Unable to launch Gradlew build. I’ve tried everything, but it won’t launch.

FAILURE: Build failed with an exception.

  • Where:
    Settings file ‘C:Usersraneeterencioothil96Downloadsforge-1.20.1-47.4.0-mdksettings.gradle’ line: 14

  • What went wrong:
    An exception occurred applying plugin request [id: ‘net.minecraftforge.gradle’, version: ‘6.0.25’]

Failed to apply plugin ‘net.minecraftforge.gradle’.
class org.gradle.initialization.DefaultSettings_Decorated cannot be cast to class org.gradle.api.Project (org.gradle.initialization.DefaultSettings_Decorated and org.gradle.api.Project are in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader @4cc77c2e)

  • Try:

Run with –stacktrace option to get the stack trace.
Run with –info or –debug option to get more log output.
Run with –scan to get full insights.
Get more help at https://help.gradle.org.

BUILD FAILED in 11s

C:Usersraneeterencioothil96Downloadsforge-1.20.1-47.4.0-mdk>

Javascript Help – Article Counter Function

I’m trying to figure out how to have a little counter for my journal entries and something isn’t right (I’m still learning javascript and this is me trying to mess with some code I found, so I’m likely missing something obvious. sorry!)
The way I want it to work is by having a counter for the number of articles in any given year section. I think the issue is that it doesn’t recognise that there are articles already in the section, but I’m not entirely sure.

Any help is appreciated!

This is the display counter:

<section id="home">
  <p>choose the year you want to view!</p>
  <button onclick="goToYear('2025')">
    <span class="year">2025</span>
    <br>
    <span id="logs2025">total logs: </span>
  </button>
</section>

Here is the article section:

<section id="y2025">
  <article><p> test article test test test </p></article>
</section>

And here’s the counter:

function totalLog(year) {
  let articles = "#y" + year + "article";
  let articlesSelected = document.querySelectorAll(articles);
  let currentSpan = "logs" + year;
  let currentSpanSelected = document.getElementById(currentSpan);
  currentSpanSelected.innerText = "total logs: " + articlesSelected.length;
}