Archive of

Mocking Node.js Path Separators: The Dependency Injection Solution

The Problem

Node.js path utilities like path.sep, path.join() are hardcoded to the current platform and readonly - you can't mock them for cross-platform testing:

// Instead of this brittle approach:
function createTempFile(name: string, separator: string) {
  return 'tmp' + separator + name; // Manual string manipulation
}

// Use dependency injection:
function createTempFile(name: string, pathImpl = path) {
  return pathImpl.join('tmp', name);
}

// Now you can test both platforms reliably:
createTempFile('data.json', path.win32)  // → tmp\data.json
createTempFile('data.json', path.posix)  // → tmp/data.json

Why This Works

Node.js provides path.win32 and path.posix as separate implementations. Instead of fighting the platform dependency, embrace it through clean dependency injection. Test Windows logic on Linux, test Unix logic on Windows - no mocking needed.

网友语录 - 第44期 - 能有人共享日出、晚霞、满月、漂亮的云,是很幸福的事

这里记录我的一周分享,通常在周六或周日发布。


小青 如果你想要的东西很明确,很确定,那去往那个终点的困难是有且有限的求饶、僵等、躲避,都过不去。行则将至


郝靠谱 能有人共享日出、晚霞、满月、漂亮的云,是很幸福的事


刘子超:有的人走着走着就忘了自己为什么走,就一直往前走。(这不就是人生吗)

许子东:汉族数千年的农耕文化,以及儒家的传统,都是一亩三分地。(人们骨子/基因里害怕远行,但内心又向往远行。因此看别人远行就是某种程度的还愿了。)

想到三毛,没人会像他这样去生活,(但)很多人愿意看她这样生活。

摘自圆桌派一期刘子超谈他中亚旅行的节目


Marskay 一件值得做的事情即使做的不怎么样也是值得的


三猫 一个基本的事实:人类能够承受的压力等级(持续时间和量级)是有限的。任何人放在持续高压的应激环境里,都会变得易怒、哭泣、好斗、抑郁、失去理性。人是血肉做的,是很容易被摧毁的。一个人之所以正常,是因为ta处在正常的环境中,而不是因为ta天生是个正常人。


长毛象野生动物园 流水线工厂的工作,在纽约时报的报道描述中属于“重体力劳动”

不过在中国打工圈的普遍语境中,这属于“轻体力工作”(重体力的是矿工、建筑工、农民之类的),因为这种工作对人的体力要求其实不高(不然古今中外也不会有那么多工厂童工了)。

虽然轻体力,但是这种工作对于精神的负荷极其巨大:以我的亲身经历为例,几年前打工那会儿,有一天某岗位缺人,我被分配到了一个组装遥控器的流水线上。(这种流水线工厂没有所谓固定岗位,工人随时会因为哪儿缺人而被安排换条流水线)我们这条线的工人每人被分配到了组装四千八百多个遥控器的任务。这是什么概念呢?当时我粗略估算了一下:我厂两班倒轮替,每班工作12小时,我是夜班,凌晨12点有30分钟吃饭时间,凌晨4点有15分钟休息时间。实际工作时间11小时15分钟,也就是四万秒出头。平均需要在8秒左右组装一个遥控器。(这还没计算上上厕所之类的时间)那款遥控器的组装并不是把两片遥控器的正反壳子一拼就完事了的(如果那样,啪叽两声就可以拼好一个,8秒太宽裕),遥控器生产全流程到了我们手上时有三个部件:除了正反两片塑料壳子,还有软胶质的小颗粒(遥控器上的按钮),我需要先把按钮嵌进遥控器正面壳子预留的镂空里,然后再把反面扣好。而按钮是极软的,用力稍大就会穿过镂空从另一边掉出来,用力小了又嵌不进去。必须用巧力刚刚好把按钮留在正面壳上,然后安装反面时还不能让按钮掉出来。刚开始我甚至半天都组装不好一个,总是会让按钮掉出来。而我却需要在几乎连续的11小时15分钟内,用我的双手,把同一套组装遥控器的动作,循环重复四千八百多次。

我的同一片肌肉,需要把同一类发力,反复做四千八百多次,每次需要在八秒之内做完。肌肉在工作后期的劳累,在这儿反而是次要的(虽然把同一小片肌肉重复使用四千多次还是很恐怖)。重要的是,我的大脑必须要集中精力,在内心把这同一套流程思考四千多次。这比把同一个单词抄写四千次恐怖多了。毕竟单词写多了有肌肉记忆,后期脑子不动也能写出来。组装遥控器不太行,走神了就组装不准。我身边的工友们,各个都是身经百战的,不像我这种打工菜鸟。有的人是组装得飞快,但是每天到了上班后期也是精神累大于身体累(而且肌肉的疲劳也会反馈给精神),许多人都打瞌睡。那一天我累死累活干到快下班,也只组装了两千多个遥控器。(让我震撼的是,我身边有的工友居然提前几个小时就做完了一天的...,做完以后开始摸鱼磨洋工……有人最终组装了五千多个)


最后临近下班时,是其中一个好心的工友送了我几百个组装好的遥控器,让我当成我的产量交上去交差,帮我凑够了三千六百多个的及格线,才让我那天勉强混过关……
(当时我感动得都要哭出来了。我跟那个工友几乎完全不认识……连话都没有说过几次,他却把自己的工作产出一下子送给我那么多)
(我对我的工友们的印象是,普遍都长着一张极其老实巴交的脸,性格也普遍特别老实。后来我想明白,不老实的人根本没有那种心力长期在工厂里吃这个苦。我进厂后最大的收获之一就是打破了我对农民工的各种刻板印象。)
那次打短工经历以后,我就特别特别理解三和大神为什么宁愿睡大街、吃挂壁面,也要多休息,也要少打工。


奧斯陸 花两百块钱买了个 3M 的强力降噪耳机(图中 X5A),真牛逼,戴上瞬间跟聋了一样,里面也有空间再戴个 AirPods Pro,这下真的清净了...


在森林里看花晒太阳的小熊 我有权优先照顾自己的需求和生活,就像树根有权优先滋养自己而非相邻的灌木


造物主知道有太多事情不能两全, 所以给浮生们留下了做梦的空间


我们只担心学生使用 AI 做作业,事实上,更应该担心的是教师使用 AI

很多老师为了节省时间,正在用 AI 快速生成低质量、毫无意义的 PPT 和讲义。


我这辈子认识的聪明人,没有一个是不大量读书的,一个也没有。巴菲特的阅读量之大,会让你大吃一惊。我的孩子们甚至嘲笑我,是一本伸出几条腿的书。

-- 芒格


虽然说对于装睡的人谁也叫不醒,但持续输出的话总能影响到那些有救的人


他过来诋毁我,其实是对我的一种仰视

周海媚

Technical Note: Testing ILogger with NSubstitute

1. The Challenge

Directly verifying ILogger extension methods (e.g., _logger.LogError("...")) with NSubstitute is difficult. These methods resolve to a single, complex generic method on the ILogger interface, making standard Received() calls verbose and brittle.

void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);

2. The Solution: Inspect the State Argument

The most robust solution is to inspect the state argument passed to the core Log<TState> method. When using structured logging, this state object is an IEnumerable<KeyValuePair<string, object>> that contains the full context of the log call, including the original message template.

3. Implementation: A Reusable Helper Method

To avoid repeating complex verification logic, create a static extension method for ILogger<T>.

LoggerTestExtensions.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;
using NSubstitute;
using FluentAssertions;

public static class LoggerTestExtensions
{
    /// <summary>
    /// Verifies that a log call with a specific level and message template was made.
    /// </summary>
    /// <typeparam name="T">The type of the logger's category.</typeparam>
    /// <param name="logger">The ILogger substitute.</param>
    /// <param name="expectedLogLevel">The expected log level (e.g., LogLevel.Error).</param>
    /// <param name="expectedMessageTemplate">The exact message template string to verify.</param>
    public static void VerifyLog<T>(this ILogger<T> logger, LogLevel expectedLogLevel, string expectedMessageTemplate)
    {
        // Find all calls to the core Log method with the specified LogLevel.
        var logCalls = logger.ReceivedCalls()
            .Where(call => call.GetMethodInfo().Name == "Log" &&
                           (LogLevel)call.GetArguments()[0]! == expectedLogLevel)
            .ToList();

        logCalls.Should().NotBeEmpty($"at least one log call with level {expectedLogLevel} was expected.");

        // Check if any of the found calls match the message template.
        var matchFound = logCalls.Any(call =>
        {
            var state = call.GetArguments()[2];
            if (state is not IEnumerable<KeyValuePair<string, object>> kvp) return false;
            
            return kvp.Any(p => p.Key == "{OriginalFormat}" && p.Value.ToString() == expectedMessageTemplate);
        });

        matchFound.Should().BeTrue($"a log call with the message template '{expectedMessageTemplate}' was expected but not found.");
    }
}

4. How to Use in a Unit Test

Step 1: Arrange In your test, create a substitute for ILogger<T> and inject it into your System Under Test (SUT).

// In your test class
private readonly ILogger<MyService> _logger;
private readonly MyService _sut;

public MyServiceTests()
{
    _logger = Substitute.For<ILogger<MyService>>();
    _sut = new MyService(_logger);
}

Step 2: Act Execute the method that is expected to produce a log entry.

[Fact]
public void DoWork_WhenErrorOccurs_ShouldLogError()
{
    // Act
    _sut.DoWorkThatFails();

    // ...
}

Step 3: Assert Use the VerifyLog extension method for a clean and readable assertion.

    // Assert
    _logger.VerifyLog(LogLevel.Error, "An error occurred while doing work for ID: {WorkId}");
}

5. Key Advantages of This Approach

  • Robustness: It verifies the intent (the message template) rather than the final formatted string, making it resilient to changes in parameter values.
  • Readability: The test assertion _logger.VerifyLog(...) is clean, concise, and clearly states what is being tested.
  • Reusability: The extension method can be used across the entire test suite.
  • Precision: It correctly targets the specific log level and message, avoiding ambiguity.

网友语录 - 第43期 - 相信别人是真的,相信别人是善的,就能为这个世界增加一些真和善

这里记录我的一周分享,通常在周六或周日发布。


碗君西木子 不要困在情绪牢笼里,always move on.


dust 很多以前的人所经历过的是后来人远远无法想象的。我有一个姑外祖母,抗战时代她十几岁,兵荒马乱家里太穷,就把她许给了个地主家当小老婆。她脾气好,一直逆来顺受,可是就在送她去夫家那天,她来了个大逃亡,不知道跑到哪里去了。她家就派人到处找,找了几天听熟人说看见她在某小镇讨饭。一伙人又追了过去,看见她和另外一个邻村的女孩一起躲在路边,她们看见人来了,撒腿就跑,一直跑到长江边。那个女孩不敢跑了,姑外祖母则纵身跳到江里。我外曾祖父也跳了下去,把她捞了上来。据说她回家的一路像一只不服但又无可奈何的小野兽,喉管里发咕咕的低吼。所有人都以为她疯了。后来她的事情传开了,人家也不要她了,她就一副神神叨叨的样子。再后来她嫁给了一个国军的小军官,这人天天打她,她夜夜鬼哭狼嚎。某一天军官突然跑了,她无儿无女,一个人守个破房子,饱一顿饥一顿。等我外曾祖父经济条件好转了点,看她可怜就把她接回了家。她有个小布袋子,视为珍宝,天天放在贴身衣物里,谁也不知道里面是啥。无数年过去了,她的精神状态时好时坏,外曾祖父去世后,照顾她的任务就交给了我外公。我小时候在外公家看到她当她是怪物,有点怕她,但她对我很友善,还拿过糖给我吃。有次我看见她偷偷捧着她的小袋子哭,就跟外公说,外公也不吱声。我就一直好奇,那袋子里到底是啥?2007年,我跟姑外祖母聊过天,她已经垂垂老矣,身体佝偻,像一棵马上就要断的老树。但她说话时却很平静,头脑比我预想的清醒。我说我能理解她当年为什么跑?她哭了出来。我就看着不说话。她哭了会儿,就从她的衣服里拿出并打开了那个袋子。里面是一张老照片,我一看,是她那年逃跑时和那个邻村的女孩合拍的照片。两个人脸上挂着微笑,都很漂亮,我一下就懂了。她看到我的眼神也知道我懂了。我没跟别人说过这事。过了几年她死的时候,葬礼上有人要拿走那个袋子,以为有什么钞票,我阻止了,最后就连她的人一起烧了。我现在有时还会想起那张照片,其实哪里有那么多怪异的人,都是被时代压变形的。我也很庆幸跟姑外祖母聊过,也许有一个人知道她的事,并且没有表示蔑视,对她来说这是一生中得到的唯一一次安慰


何帆: 听美国法官吐槽

与纽约皇后区刑事法院的法官交流时,我问他们,作为法官,内心对陪审团到底是何态度,是宁愿自己审,还是交陪审团定罪。一位老法官意味深长地回答:“这得看怎么讲了。说好听点,12个人的智慧,总比1个人的高明。说难听点,黑锅由12个人背,总比1个人背强。”


普法:对于权力的行使而言,法无明文授权即为禁止;对于权利的享有而言,法无明文禁止即允许


5 things a Stoic accepts early:

– Life isn’t fair – People are flawed – Pain is a teacher – Control is limited – Time is running out


小青 我发现了一件很奇妙的事情,我好像不需要问自己是什么情绪,我只需要问自己是不是在害怕就行了。每当我发现自己行为不对劲的时候,把“你应该”、“你必须”换成“你害怕吗?具体怕什么?”大多数时候就能准确把握自己的情绪。但是这样呢,又遇到另一个问题,就是当我的恐惧被自己看到的时候呢,我相信对我的长期身心健康有好处,但短期内我会立刻瘪嘴就哭……就要么是个内化了的糟糕父母,要么是个随地大哭的小孩子。我不知道怎么办才好。

我好像知道为什么我小时候我一哭我爸妈就打我了,因为他们不知道怎么应对一个哭泣、失控的孩子,我哭的时候,他们也不知道怎么办才好。他们没有“成功”过,他们只是看到那些他们见过的成功人士看起来都情绪稳定、勤奋坚强,不知道怎么成为和培养那样的人,就只能从表面上把我规定成那个样子。但我天生就不是一个“有种你打死我”的人,我从小对任何暴力都极其恐惧,看唐僧被妖精抓走我都换台。于是他们用不着下狠手打我,只要骂脏话、命令我自己去跪搓衣板就能轻易用恐惧控制我的行为。

有时候我觉得这种无所不在的恐惧不是我一个人的,而是我祖上很多代的农民和城镇平民写在基因里,我绝大多数的同胞写在文化和语言里的底噪


到爱尔兰的头两个星期,维特根斯坦住在都柏林的罗斯旅馆。只要医院里没事,德鲁利就陪维特根斯坦到都柏林城里或周边寻找可能的住处。没地方能提供他需要的孤独和平静, 但德鲁利在圣帕特里克医院的朋友罗伯特·麦卡洛夫暂时解决了这个问题。麦卡洛夫常去维克洛郡瑞德克洛斯的一处农舍度假,房子属于理查德·金斯顿和詹妮·金斯顿,他们对他说过想招一个永久房客。这个信息传给了维特根斯坦,他立刻从都柏林动身去“勘察现场(case the point)”(这时候他的词汇里包含了从美国侦探小说里借来的一点新鲜用语)。维克洛郡迷住了他。“坐公车前往的路上,”回来后他告诉德鲁利,“我不停地对自己说,真是个真正美丽的国度。”

不过,搬进金斯顿夫妇的农舍后没多久,他就写信对里斯说自己在那儿感到“冷和不舒服”: “我也许会在几个月之内搬到西爱尔兰的某个隔绝得多的地方。” 但几个星期后他适应多了, 德鲁利第一次去瑞德克洛斯时, 看起来一切都很好。维特根斯坦告诉他: “有时我的想法来得如此迅速,我觉得仿佛有什么在引导着我的笔。现在我清楚地看到,放弃教授职位是正确的。在剑桥我永远做不完这工作。”

维特根斯坦传


他说穆尔克里斯全家都是不想干任何活的人。他震惊地看到,虽是个出色的女裁缝,托米的母亲却衣衫褴褛地晃悠,托米自己虽是个合格的木匠,但他们屋子里的每一张椅子都有一条断腿。他在日记里径直说托米——“我在这儿完全依赖于他”——是“不可靠的”。

无论可不可靠,托米是他有的一切。他最近的邻居莫蒂默一家认为他完全疯了,不愿跟他有任何关系。他们甚至禁止他走进他们的地界,理由是他会吓坏他们的羊。因此,如果他想到罗斯洛后面的山岗上走走,就不得不走一条长而迂回的路线。有一次他这样散步时,莫蒂默家的人看到他突然停住,用手杖当工具在路上的泥地里画一个轮廓图(一个兔-鸭图?),他站着,长时间全神贯注盯着这张图,然后又走了起来。这事印证了他们最初的看法。还有一件事也是如此:一天晚上,莫蒂默家的狗叫声打乱了维特根斯坦的专注,他猛烈爆发了。事实上,他留给莫蒂默家的印象,与他先前留给奥地利乡下村民的印象颇为雷同。

托米也觉得维特根斯坦有点怪。但部分因为对德鲁利家的忠诚(迈尔斯·德鲁利曾跳下船救出了溺水的托米),部分因为开始喜欢“教授”的相伴,他愿意竭尽所能使维特根斯坦在罗斯洛的居住尽量舒适和愉快。例如,他尽了最大的努力满足维特根斯坦严格的清洁和卫生标准。按维特根斯坦的建议,他每天早晨不只送去牛奶和煤,还送去自己用过的茶叶。每天早晨,茶叶洒在厨房地板上吸污垢,然后扫掉。维特根斯坦还叫托米弄掉屋子里的“甲壳动物”(土鳖虫)。托米的做法是,给整个屋子喷了多到令人窒息的消毒粉。毕生害怕每一种虫子的维特根斯坦对结果感到满意,他情愿面对窒息的威胁,也不愿意看见土鳖虫。

罗斯洛农舍有两个房间, 一个卧室和一个厨房, 维特根斯坦的大部分时间在厨房里度过。但没用厨房做饭。在罗斯洛时,他几乎完全依靠从戈尔韦的一家杂货店里订购的罐头食品。托米挺担忧他的饮食。“罐头食品会吃死你”, 他有一次说。维特根斯坦的回答是阴森的: “反正人活得太久了”。维特根斯坦把厨房改作书房, 托米早晨去时, 常常发现他坐在厨房的桌子边, 往夹起来的散页上写着什么。几乎每天都有一堆丢掉的纸页, 烧掉它们是托米的活。

一天早晨,托米到罗斯洛时听见维特根斯坦的说话声, 进屋后惊讶地发现只有“教授”自个。“我以为你有个伴在这儿呢,”他说。“我是有,” 维特根斯坦回答, “我在跟我的一个很亲爱的朋友——我自己——谈话。” 在他这时期的一本笔记本里, 这句话得到了呼应:

几乎我的所有写作都是跟我自己的私人谈话。我跟自己促膝而谈的话。


在森林里看花晒太阳的小熊 过度苛求自己完美,以过高标准要求和评判自己是人生早期不健康环境下形成的求生策略与机制,是出于自我保护的原因形成的。 现在我们已经长大啦,拥有了更多的空间、选择权、能力和自由,不再随时受生存威胁控制和胁迫,就要适度地学习给自己松绑让自己放松,学会看清自己的正当性,表述和满足自己的需求,这对我们的塑造完整的人格以及创造幸福的生活有着无可替代的价值。

在森林里看花晒太阳的小熊 对害怕休息和停顿,会push自己不断前进思考的朋友说:

休息不是拒绝,而是为了更深的“接受"。 休息不是停顿,而是为了更好更有能量地出发

休息和战斗、开拓、创新、改革同等神圣。 你值得通过休息获得滋养与修复。

宇宙会为我们保留所有真正属于我们的机会。


王海鹏Seal:不认为自己对,就不会认为别人错。不认为自己知道,就不会认为别人无知。不认为自己智商高,就不会认为别人是白痴。


不管是哪一层的领导,在分配一件任务的时候,请尽可能做到让这件事情可评估。这既有利于任务接受者把事情保质保量的做好,也有助于你得到有效的反馈。


相信别人是真的,相信别人是善的,就能为这个世界增加一些真和善


小青 游记一定要三天内写出来,哪怕非常粗糙!!这么好看的塔我已经不记得是哪里拍的了……


我对夸大创造力的说辞持怀疑态度:我认为首先你需要以精确、技术、具体和现实感为基础。只有在某种平凡坚固的基础上才能产生创造力:幻想就像果酱,你必须把它涂在一片结实的面包上。如果没有,它仍然是一个无形的东西,你不能拿它做任何东西

—伊塔洛•卡尔维诺(Italo Calvino)


Geek

有点意思,居然还有这种脚本 《基于Debian搭建HomeNAS》将 Debian 系统快速配置成准 NAS 系统。

https://github.com/kekylin/debnas

轻松实现文件共享、照片备份、家庭影音、管理Docker、管理虚拟机、建立RAID等功能,使得Debian系统能够高效稳定地承担NAS任务。


Morris

人生不需要有意义,需要的是有意思。


陈同辉 说一下吧,普通人,没有背景的普通人,一定要知道,你个人价值的体现,必须在商业化的环境中才能实现。不要去抵制商业化,真正喜欢非商业化的,是有权的人,因为只有在非商业化环境,有权的人才可以以非常廉价的方式,调动、占用、劳役资源(包括劳动力资源),所以权力是天然反对商业化的。 而普通人,在非商业化的环境中,劳动力是一文不值的,这会导致劳动力的载体“生命”也是不值钱的。如果劳动力不值钱,生命不值钱,你所有的奋斗和勤奋,也一文不值,因为你所有的奋斗和勤奋的成果,会被权力无偿占有。 大家去想想,无论是从一个国家内部,还是不同国家之间对比,是不是如此? 再进一步,普通人,一定要去对资本友好的地方,具体就不展开了,这些东西,能懂的,自然想的明白。

Flutter iOS Safari Double Selection Issue - Technical Workaround

Problem

Flutter web apps on iOS Safari exhibit a "double selection" bug where text selection creates two overlapping selection layers, causing visual artifacts and interaction issues.

Root Cause

iOS Safari creates both native browser text selection AND Flutter's custom SelectionArea selection simultaneously, resulting in conflicting selection states.

Working Workaround

HTML Solution (web/index.html)

<head>
<style>
  * {
	-webkit-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
	/* Disable caret to prevent selection artifacts */
	caret-color: rgba(255, 255, 255, 0) !important;
  }
</style>
</head>
<body oncontextmenu="event.preventDefault();" >
...

Flutter Integration

// In main.dart or app initialization
import 'package:flutter/gestures.dart';

void main() {
    // Disable browser context menu to let Flutter handle selection
    if (kIsWeb) {
        BrowserContextMenu.disableContextMenu();
    }
    runApp(MyApp());
}

How It Works

  1. Disables native selection - user-select: none prevents Safari's text selection
  2. Blocks touch events - Prevents iOS touch selection gestures
  3. Maintains Flutter selection - BrowserContextMenu.disableContextMenu() allows Flutter's SelectionArea to work
  4. Hides caret artifacts - caret-color: transparent eliminates visual glitches

Trade-offs

  • ❌ Breaks native web text selection outside Flutter widgets
  • ❌ May affect accessibility tools
  • ✅ Provides consistent cross-platform selection behavior
  • ✅ Eliminates iOS-specific double selection bug

Status

This workaround is recommended for Flutter web apps requiring text selection on iOS Safari until the official framework fix is released.